Data Downloading

sratools

We downloaded the data using SRAtools on grace using the scripts generated by the following codes. We downloaded the sratools from github and installed it on grace with setup. All files downloaded were not zipped so we also zipped each file into fastq.gz file.

id.list<-readLines("/Users/yanxiting/Downloads/SRR_Acc_List(2).txt")
temp1<-paste("/home/xy48/scratch_palmer/sratoolkit/sratoolkit.3.0.0-ubuntu64/bin/prefetch.3.0.0 -X 9999999999999 ",id.list,"\n",sep="")
temp2<-paste("/home/xy48/scratch_palmer/sratoolkit/sratoolkit.3.0.0-ubuntu64/bin/fastq-dump.3.0.0 --split-files ./",id.list,".sra\n",sep="")
temp3<-rep("cd /home/xy48/scratch_palmer/SARC_10x/test/sra\n",length(temp1))
temp4<-paste("gzip ./",id.list,"_*.fastq\n",sep="")
temp5<-rep("ssh transfer\n",length(temp1))
cmd.out<-cbind(temp5,temp1,temp3,temp2,temp4)
temp.cmd<-apply(cmd.out,1,paste,collapse="",sep="")
cat(temp.cmd,file="./download_commands.txt",append=F,sep="")

changing names

The cell ranger pipeline recognize fastq file names are formated as [Sample Name]S1_L00[Lane Number][Read Type]_001.fastq.gz. There are 3 files per sample representing I1, R1 and R2 downloaded by sratools, which were named as *_1, *_2 and *_3.fastq files. To correctly run cell ranger on these files, we need to change the fastq.gz names.

# this was run on grace.
source("~/Rprogram/my_functions.R")
data.dir<-"/home/xy48/scratch_palmer/SARC_10x/test/sra"
filenames<-list.files(data.dir)
temp<-sapply(filenames,my.element.extract,splitchar="\\.",index=3)
filenames<-filenames[temp=="gz"]
filenames<-filenames[!is.na(filenames)]

for(i in 1:length(filenames)){
  
  if(substr(filenames[i],1,4)=="SRR9"){
    from.filename<-filenames[i]
    temp<-my.element.extract(filenames[i],splitchar="\\.",index=1)
    temp<-my.element.extract(temp,splitchar="_",index=2)
    if(temp=="1"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_I1_001.fastq.gz")
    }
    if(temp=="2"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_R1_001.fastq.gz")
    }
    if(temp=="3"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_R2_001.fastq.gz")
    }
  
    file.rename(from=file.path(data.dir,from.filename),to=file.path(data.dir,to.filename))
    cat("from=",file.path(data.dir,from.filename),"\n","to=",file.path(data.dir,to.filename),"\n",sep="")
  }else{
    
    from.filename<-filenames[i]
    temp<-my.element.extract(filenames[i],splitchar="\\.",index=1)
    temp<-my.element.extract(temp,splitchar="_",index=2)
    if(temp=="1"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_R1_001.fastq.gz")
    }
    if(temp=="2"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_R2_001.fastq.gz")
    }
    if(temp=="3"){
        to.filename<-paste0(my.element.extract(filenames[i],splitchar="_",index=1),"_S1_L001_I1_001.fastq.gz")
    }
  
    file.rename(from=file.path(data.dir,from.filename),to=file.path(data.dir,to.filename))
    cat("from=",file.path(data.dir,from.filename),"\n","to=",file.path(data.dir,to.filename),"\n",sep="")    
    
  }
}

cell ranger

We rsync the fastq.gz files onto farnam under ~/scratch60/SARC_10X/fastq and run cell ranger on the files using commands generated by the following codes.

id.list<-readLines("/Users/yanxiting/Downloads/SRR_Acc_List(2).txt")
temp1<-paste("#!/bin/bash\n#SBATCH --time=24:00:00  --ntasks=1 --partition=general --cpus-per-task=8 --mem=80GB --job-name=",id.list," -o /home/xy48/scratch60/SARC_10X/cellranger_scripts/",id.list,".sh.o%J -e /home/xy48/scratch60/SARC_10X/cellranger_scripts/",id.list,".sh.e%J\n",sep="")
temp2<-rep("module load cellranger/5.0.0\nmodule load bcl2fastq2/2-20-0-foss-2018b\n",length(id.list))
temp3<-paste("cd /home/xy48/scratch60/SARC_10X/cellranger_results\n")
temp4<-paste("cellranger count --id=",id.list," --fastqs=/home/xy48/scratch60/SARC_10X/fastq --transcriptome=/home/xy48/scratch60/SARC_10X/refdata-gex-GRCh38-2020-A --localcores=8 --localmem=60 --sample=",id.list,"\n",sep="")
cmd.out<-cbind(temp1,temp2,temp3,temp4)
temp.cmd<-apply(cmd.out,1,paste,collapse="",sep="")
cat(temp.cmd,file="./cellranger_scripts.txt",append=F,sep="")

There are samples with multiple runs. We run cellranger aggr to merge the multiple runs into one single nUMI vector.

source(file.path(home.dir,"Rprogram/my_functions.R"))
library(xlsx)
home.dir<-"/home/yanxiting/driver_Farnam"
runtable.filepath<-file.path(home.dir,"scratch60/SARC_10X/SraRunTable.txt")
id.filepath<-file.path(home.dir,"scratch60/SARC_10X/scRNA_ids.xlsx")

run.table<-read.table(runtable.filepath,sep="\t",header=T,check.names = F)
id.table<-read.xlsx(id.filepath,sheetIndex = 1,check.names=F)

temp.table<-run.table[run.table$`GEO_Accession (exp)`%in%as.matrix(id.table)[,1],]
temp.list<-split(as.matrix(temp.table)[,1],temp.table$`GEO_Accession (exp)`)

# extract the samples with multiple runs
temp.list<-temp.list[unlist(lapply(temp.list,length))>1]

# find out the fastq part these IDs belong to
temp.filenames<-list.files(file.path(home.dir,"scratch60/SARC_10X"))
temp.filenames<-temp.filenames[grep(".filelist",temp.filenames)]
temp.filenames<-temp.filenames[grep("fastq",temp.filenames)]

file.list<-list()
for(i in 1:length(temp.filenames)){
  temp<-readLines(file.path(home.dir,"scratch60/SARC_10X",temp.filenames[i]))
  temp<-unname(sapply(temp,my.element.extract,splitchar="/",index=-1))
  temp<-unname(sapply(temp,my.element.extract,splitchar="_",index=1))
  temp<-unique(temp)
  file.list[[i]]<-temp
}
names(file.list)<-temp.filenames
temp<-character()
for(i in 1:length(file.list)){
  temp<-c(temp,rep(names(file.list)[i],length(file.list[[i]])))
}

file.list.matrix<-cbind(unlist(file.list),temp)

temp<-list()
for(i in 1:length(temp.list)){
  temp[[i]]<-unique(file.list.matrix[file.list.matrix[,1]%in%temp.list[[i]],2])
}

# transfer the cellranger results back to farnam
# generate the aggregation CSV file
cellranger.dir<-file.path(home.dir,"scratch60/SARC_10X/cellranger_results")
output.dir<-file.path(home.dir,"scratch60/SARC_10X/cellranger_aggr_scripts")

for(i in 1:length(temp.list)){
  output.filepath<-file.path(output.dir,paste(names(temp.list)[i],"_aggr.csv",sep=""))
  # pipeline before cellranger 6.0, use library. Otherwise, use sample_id
  cmd.out<-c("library_id","molecule_h5")
  cmd.out<-rbind(cmd.out,cbind(temp.list[[i]],paste("/home/xy48/scratch60/SARC_10X/cellranger_results/",temp.list[[i]],"/outs/molecule_info.h5",sep="")))
  write.table(cmd.out,file=output.filepath,row.names=F,col.names=F,sep=",",append=F,quote=F)
}

# generate the sh file to run cellranger aggr on the replicated runs of the same sample.
script.dir<-file.path(home.dir,"scratch60/SARC_10X/cellranger_aggr_scripts")
result.dir<-file.path(home.dir,"scratch60/SARC_10X/cellranger_aggr_results")

for(i in 1:length(temp.list)){
  csv.filepath<-file.path(script.dir,paste(names(temp.list)[i],"_aggr.csv",sep=""))
  script.filepath<-file.path(script.dir,paste(names(temp.list)[i],".sh",sep=""))
  cmd.out<-paste("#!/bin/bash\n#SBATCH --time=24:00:00  --ntasks=1 --partition=general --cpus-per-task=1 --mem=80GB --job-name=",names(temp.list)[i]," -o /home/xy48/scratch60/SARC_10X/cellranger_aggr_scripts/",names(temp.list)[i],".sh.o%J -e /home/xy48/scratch60/SARC_10X/cellranger_aggr_scripts/",names(temp.list)[i],".sh.e%J\n",sep="")
  cmd.out<-paste(cmd.out,"module load cellranger/5.0.0\nmodule load bcl2fastq2/2-20-0-foss-2018b\n",sep="")
  cmd.out<-paste(cmd.out,"cd /home/xy48/scratch60/SARC_10X/cellranger_aggr_results\n",sep="")
  cmd.out<-paste(cmd.out,"cellranger aggr --id=",names(temp.list)[i]," --csv=/home/xy48/scratch60/SARC_10X/cellranger_aggr_scripts/",names(temp.list)[i],"_aggr.csv\n",sep="")
  cat(cmd.out,file=script.filepath,append=F)
}

We moved the cellranger aggr results back to cellranger_results together with the samples with unique run.

UMI matrix extraction

We extract the nUMI vector for the samples included in the original paper.

home.dir<-"/home/yanxiting/driver_Grace"
source(file.path(home.dir,"Rprogram/my_functions.R"))
library(xlsx)
runtable.filepath<-file.path(home.dir,"scratch_palmer/SARC_10x/SraRunTable.txt")
id.filepath<-file.path(home.dir,"scratch_palmer/SARC_10x/scRNA_ids.xlsx")

run.table<-read.table(runtable.filepath,sep="\t",header=T,check.names = F)
id.table<-read.xlsx(id.filepath,sheetIndex = 1,check.names=F)
WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.apache.poi.util.SAXHelper (file:/home/yanxiting/MyTools/R4.1.1/R-4.1.1/library/xlsxjars/java/poi-ooxml-3.10.1-20140818.jar) to method com.sun.org.apache.xerces.internal.util.SecurityManager.setEntityExpansionLimit(int)
WARNING: Please consider reporting this to the maintainers of org.apache.poi.util.SAXHelper
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
my.table<-run.table[run.table$`GEO_Accession (exp)`%in%as.matrix(id.table)[,1],]

filenames<-list.files(file.path(home.dir,"scratch_palmer/SARC_10x/cellranger_results"))

# use Read10x to load in the data
dir.list<-rep("",length(filenames))
sample.names<-character()
for(i in 1:length(filenames)){
  
  if(substr(filenames[i],1,1)=="G"){
    dir.list[i]<-file.path(home.dir,"scratch_palmer/SARC_10x/cellranger_results",filenames[i],"outs","count","filtered_feature_bc_matrix")
  }else{
    dir.list[i]<-file.path(home.dir,"scratch_palmer/SARC_10x/cellranger_results",filenames[i],"outs","filtered_feature_bc_matrix")
  }
  
  if(substr(filenames[i],1,1)=="G"){
    sample.names<-c(sample.names,filenames[i])
  }else{
    sample.names<-c(sample.names,as.matrix(my.table)[my.table$Run==filenames[i],"Sample Name"])
  }
 
}

# duplicated gene symbols will be changed to .1, .2, and so on.
temp<-Read10X(data.dir=dir.list,gene.column=2,cell.column=1)
temp.num<-as.numeric(sapply(colnames(temp),my.element.extract,splitchar="_",index=1))
temp1<-sample.names[temp.num]
temp2<-sapply(colnames(temp),my.element.extract,splitchar="_",index=-1)
colnames(temp)<-paste(temp1,"_",temp2,sep="")
output.filepath<-"/home/yanxiting/driver_Grace/scratch_palmer/SARC_10x/merged_nUMI_45_genenames_Read10X.rds"
saveRDS(temp,file=output.filepath,refhook=NULL)


#gcql1<-CreateSeuratObject(counts=temp)

merged.data<-numeric()
for(i in 1:length(filenames)){
  if(substr(filenames[i],1,1)=="G"){
    matrix.dir<-file.path(home.dir,"scratch_palmer/SARC_10x/cellranger_results",filenames[i],"outs","count","filtered_feature_bc_matrix")
  }else{
    matrix.dir<-file.path(home.dir,"scratch_palmer/SARC_10x/cellranger_results",filenames[i],"outs","filtered_feature_bc_matrix")
  }
  
  barcode.path <- file.path(matrix.dir, "barcodes.tsv.gz")
  features.path <- file.path(matrix.dir, "features.tsv.gz")
  matrix.path <- file.path(matrix.dir, "matrix.mtx.gz")
  mat <- readMM(file = matrix.path)
  feature.names = read.delim(features.path,
                             header = FALSE,
                             stringsAsFactors = FALSE)
  barcode.names = read.delim(barcode.path,
                             header = FALSE,
                             stringsAsFactors = FALSE)
  if(substr(filenames[i],1,1)=="G"){
    sample.name<-filenames[i]
  }else{
    sample.name<-as.matrix(my.table)[my.table$Run==filenames[i],"Sample Name"]
  }
  
  colnames(mat) = paste0(sample.name,"_",barcode.names$V1)
  rownames(mat) = feature.names$V2
  
  if(i==1){
    merged.data<-mat
    gene.names<-rownames(mat)
  }else{
    merged.data<-cbind(merged.data,mat[gene.names,])
  }
}

output.filepath<-"/home/yanxiting/driver_Grace/scratch_palmer/SARC_10x/merged_nUMI_45_genenames.rds"
saveRDS(merged.data,file=output.filepath,refhook=NULL)

We transferred the cellranger results back to farnam to extract the nUMI results. We also add the samples with multiple runs into the merged matrix.

# samples with multiple runs have names of the GEO accession number.
source("/home/yanxiting/driver_Farnam/Rprogram/my_functions.R")
library(Matrix)
cellranger.dir<-"/home/yanxiting/driver_Farnam/scratch60/SARC_10X/cellranger_results"
runtable.filepath<-"/home/yanxiting/driver_Farnam/scratch60/SARC_10X/SraRunTable.txt"

# load in the run table
run.table<-read.table(runtable.filepath,sep="\t",header=T,check.names=F,stringsAsFactors = FALSE)

# load in the run names and the GSM IDs under the cell ranger results folder
temp.filenames<-list.files(cellranger.dir)

sample.rep<-temp.filenames[substr(temp.filenames,1,3)=="GSM"]
sample.uni<-temp.filenames[substr(temp.filenames,1,3)!="GSM"]
my.run.table<-run.table[as.matrix(run.table)[,"Sample Name"]%in%sample.rep | as.matrix(run.table)[,"Run"]%in%sample.uni,]
# extract the nUMI matrix
dirnames<-list.files(cellranger.dir)
dirnames<-dirnames[substr(dirnames,1,4)=="SRR1"]

for(i in 1:length(dirnames)){
  run.name<-dirnames[i]
  sample.name<-run.table[run.table[,"Run"]==run.name,"Sample Name"]
  matrix_dir<-file.path(cellranger.dir,dirnames[i],"outs","filtered_feature_bc_matrix")
  barcode.path <- file.path(matrix_dir, "barcodes.tsv.gz")
  features.path <- file.path(matrix_dir, "features.tsv.gz")
  matrix.path <- file.path(matrix_dir, "matrix.mtx.gz")
  mat <- readMM(file = matrix.path)
  feature.names = read.delim(features.path,
                             header = FALSE,
                             stringsAsFactors = FALSE)
  barcode.names = read.delim(barcode.path,
                             header = FALSE,
                             stringsAsFactors = FALSE)
  colnames(mat) = paste0(sample.name,"_",barcode.names$V1)
  rownames(mat) = feature.names$V1
  
  output.filepath<-file.path("/home/yanxiting/driver_Farnam/scratch60/SARC_10X/cellranger_results",paste0(sample.name,"_",run.name,"_nUMI.rds"))
  saveRDS(mat,file=output.filepath,refhook=NULL)
}

Since we have limited storage on the hpc, we back up the fastq.gz files and the cell ranger output folders. We extracted the nUMI matrix of each sample separately and deleted all other files to make room. All fastq.gz files and their corresponding cell ranger output folders are backed up on google drive under /Volumes/GoogleDrive/My Drive/GraceBackup/GRADS/SARC_PBMC/SARC_10X.

Seurat Analysis

We load the data into Seurat for downstream analysis, especially for the integrated analysis.

First, we create the Seurat object and preliminary filtering on genes and

# due to the unavailability of the cluster, we ran these codes on tac4 server with the same versions of R and packages
# srun --pty --x11 -p pi_kaminski -t 12:00:00 --ntasks=1 --nodes=1 --cpus-per-task=1 --mem=60152 bash
# module restore seurat
#home.dir<-"/home/yanxiting/driver_Farnam"
home.dir<-"/home/yanxiting/Documents/Research/GRADS_SARC_PBMC"
source("/home/yanxiting/Rprogram/my_functions.R")

#output.dir<-file.path(home.dir,"scratch60/SARC_10X/Seurat")
output.dir<-file.path(home.dir,"scRNA-seq/Seurat")

if(file.exists(output.dir)==F){
dir.create(output.dir)
}

# Load the merged data
merged.data<-readRDS(file.path(home.dir,"Data/merged_nUMI_45_genenames_Read10X.rds"),refhook=NULL)

#geoff.data <- Read10X(data.dir = file.path(data.dir,"outs","filtered_gene_bc_matrices_mex","GRCh38"))
#load(file.path(home.dir,"scratch_kaminski/public/Backup/Jonas/R_objects/10x_ChuppAsthma.mtx.hybrid_gene_symbols_wo_background_03_1119.Robj"))
gcql1 <- CreateSeuratObject(counts = merged.data, project = "SARC_10X",min.cells = 3,  min.features = 200)
gcql1[["percent.mt"]] <- PercentageFeatureSet(gcql1, pattern = "^MT-")

cat("\n")
cat("Originally, there are ",nrow(merged.data)," genes and ",ncol(merged.data)," cells in the data.\n",sep="")
Originally, there are 36601 genes and 115826 cells in the data.
cat("After the first step filtering, there are ",nrow(gcql1@assays$RNA@data)," genes and ",ncol(gcql1@assays$RNA@data)," cells in the filtered data.\n",sep="")
After the first step filtering, there are 23851 genes and 73145 cells in the filtered data.
rm(merged.data)
#gc(verbose=F)
invisible(gc())

# load in the phenotype data of all the samples
id.filepath<-file.path(home.dir,"Data/scRNA_ids.xlsx")
runtable.filepath<-file.path(home.dir,"Data/SraRunTable.txt")

run.table<-read.table(runtable.filepath,sep="\t",header=T,check.names = F)
id.table<-read.xlsx(id.filepath,sheetIndex = 1,check.names=F)

# get the sample names for all the cells
my.ident<-unname(sapply(colnames(gcql1@assays$RNA@counts),my.element.extract,splitchar="_",index=1))
names(my.ident)<-colnames(gcql1@assays$RNA@counts)

temp<-split(as.matrix(run.table)[,"study_classification"],as.matrix(run.table)[,"Sample Name"])
temp<-lapply(temp,unique)
my.disease<-unlist(temp[my.ident])

my.samplenames<-my.ident
disease.samplenames<-paste(my.disease,"_",my.samplenames,sep="")

# scRNA-seq QC metric.
#mito.genes1 <- grep(pattern = "^MT-", x = rownames(gcql1@assays$RNA@counts), value = TRUE)
#percent.mito1 <- Matrix::colSums(gcql1@assays$RNA@counts[mito.genes1, ])/Matrix::colSums(gcql1@assays$RNA@counts)

# AddMetaData adds columns to object@meta.data, and is a great place to
# stash QC stats
gcql1 <- AddMetaData(object = gcql1, metadata = my.disease, col.name = "disease")
gcql1 <- AddMetaData(object = gcql1, metadata = my.samplenames, col.name = "sample.names")
gcql1 <- AddMetaData(object = gcql1, metadata = my.ident, col.name = "sample")
gcql1 <- AddMetaData(object = gcql1, metadata = disease.samplenames, col.name = "disease.samplenames")
temp<-rep("pbmc",nrow(gcql1@meta.data))
gcql1 <- AddMetaData(object = gcql1, metadata = temp, col.name = "tissue")

# filter the data to only keep genes present (>0 in >=1% of all the cells) in >=3 subjects
gene.pres<-numeric()
sample.names.unique<-unique(gcql1@meta.data$sample.names)

for(i in 1:length(sample.names.unique)){
  temp.matrix<-gcql1@assays$RNA@counts[,gcql1@meta.data$sample.names==sample.names.unique[i]]
  temp.vect<-apply(temp.matrix>0,1,sum)
  gene.pres<-cbind(gene.pres,temp.vect>=(ncol(temp.matrix)*0.01))
}

gene.names<-rownames(gcql1@assays$RNA@counts)[apply(gene.pres,1,sum)>=3]
gcql1.orig<-gcql1
gcql1<-subset(gcql1.orig,features=gene.names)

cat("After the first and second steps of filtering, there are ",nrow(gcql1@assays$RNA@counts)," genes and ",ncol(gcql1@assays$RNA@counts)," cells in the data.\n",sep="")
After the first and second steps of filtering, there are 12834 genes and 73145 cells in the data.

Second, we had a third filtering step on cells based on the nGene and percent.mito. Here are the plots to help us decide the threshold of the filtering.


#g1<-VlnPlot(object = gcql1, features = c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3,group.by="samplenames",combine=T)

g2<-VlnPlot(object = gcql1, features= c("nFeature_RNA", "nCount_RNA", "percent.mt"), ncol = 3,group.by = "disease.samplenames",combine=T)
grid.arrange(g2,ncol=1)

# only keep cells with nFeature_RNA>=330 or <=6000
#gcql1<-subset(gcql1,subset= nFeature_RNA >= 300 & nFeature_RNA<= 6000 & percent.mt<= 50)
gcql1<-subset(gcql1,subset= nFeature_RNA >= 300 & percent.mt<= 50)
cat("After the third filtering step, we have ",nrow(gcql1@assays$RNA@counts)," genes and ",ncol(gcql1@assays$RNA@counts)," cells.\n",sep="")
After the third filtering step, we have 12834 genes and 70244 cells.

Third, we normalize the unimputed and save the SAVER imputed data as the normalized data. These data are saved for downstream further processing.

# normalize the data and save the results
gcql1 <- NormalizeData(gcql1, normalization.method = "LogNormalize",  scale.factor = 10000)
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
saveRDS(gcql1, file = file.path(output.dir,"1_normalized_data_unimputed_1.rds"))

#gcql1.saver<-NormalizeData(object = gcql1.saver, normalization.method = "LogNormalize",  scale.factor = 10000)
#saveRDS(gcql1.saver, file = file.path(output.dir,"preprocessed_data_saverimputed_1.rds"))

cat("the preprocessed data for the unimputed data was saved as ",file.path(output.dir,"1_normalized_data_unimputed_1.rds"),"\n",sep="")
the preprocessed data for the unimputed data was saved as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/1_normalized_data_unimputed_1.rds
cat("After the normalization, there are ",nrow(gcql1@assays$RNA@data)," genes and ",ncol(gcql1@assays$RNA@data)," cells\n",sep="")
After the normalization, there are 12834 genes and 70244 cells
#cat("the preprocessed data for the SAVER imputed data was saved as ",file.path(output.dir,"preprocessed_data_saverimputed_1.rds"),"\n",sep="")

Original Cell Clustering

First, we find variable genes.

features.n<-2000
home.dir<-"/home/yanxiting/Documents/Research/GRADS_SARC_PBMC"

We load in the normalized unimputed data and find the top variable genes for cell clustering. The normalized data was further scaled to remove the effect of nUMI and percent.mito for further cell clustering purpose.

# load in the normalized unimputed data
data.filepath<-file.path(home.dir,"scRNA-seq/Seurat/1_normalized_data_unimputed_1.rds")
output.dir<-file.path(home.dir,"scRNA-seq/Seurat")

if(file.exists(output.dir)==F){
  dir.create(output.dir)
}

gcql1<-readRDS(file = data.filepath, refhook = NULL)
gcql1<-FindVariableFeatures(gcql1,selection.method="vst",nfeatures=features.n)
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
# Identify the 10 most highly variable genes
top10 <- head(VariableFeatures(gcql1), 10)

# plot variable features with and without labels
plot1 <- VariableFeaturePlot(gcql1)
plot2 <- LabelPoints(plot = plot1, points = top10, repel = TRUE)
When using repel, set xnudge and ynudge to 0 for optimal results
CombinePlots(plots = list(plot1, plot2))

Then we scale the data to remove the effect from nUMI and percent.mito. Here we decide to scale for all genes so that later on, the heatmap will be generated using the scaled data.

#maybe regress cell cycle http://satijalab.org/seurat/cell_cycle_vignette.html
all.genes<-rownames(gcql1)
#gcql1 <- ScaleData(gcql1,features = all.genes, vars.to.regress = c("nCount_RNA", "percent.mt"))
gcql1 <- ScaleData(gcql1)
#output.filepath<-file.path(output.dir,"2_scaled_allgenes_unimputed.rds")
#saveRDS(gcql1, file = output.filepath)

We perform the principal component analysis on the variable genes to decide the number of PCs to use for clustering.

gcql1 <- RunPCA(object = gcql1, features = VariableFeatures(gcql1), npcs=200,verbose=F)

To see if there are PCs that are subject specific, the 2D PCA plots using the top 3 PCs are as follows for the un-imputed data.

#par(mfrow=c(2,2))
fig.1<-DimPlot(object = gcql1, dims = c(1,2), pt.size=0.5)
fig.2<-DimPlot(object = gcql1, dims = c(2,3), pt.size=0.5)
fig.3<-DimPlot(object = gcql1, dims = c(1,3), pt.size=0.5)
grid.arrange(fig.1,fig.2,fig.3,ncol=1)

We draw the heatmap of the top genes for each PC.

DimHeatmap(gcql1, dims = 1:10, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 11:20, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 21:30, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 31:40, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 41:50, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 51:60, cells = 500, balanced = TRUE)

DimHeatmap(gcql1, dims = 61:70, cells = 500, balanced = TRUE)
DimHeatmap(gcql1, dims = 71:80, cells = 500, balanced = TRUE)

We use the following Jackstraw plots and the Elbowplot to decide the number of PCs to use for downstream analysis.

gcql1 <- JackStraw(object = gcql1, num.replicate = 100, verbose = TRUE,dims=200)

  |                                                  | 0 % ~calculating  
  |+                                                 | 1 % ~02d 04h 52m 05s
  |+                                                 | 2 % ~02d 05h 28m 37s
  |++                                                | 3 % ~02d 05h 09m 39s
  |++                                                | 4 % ~02d 05h 35m 14s
  |+++                                               | 5 % ~02d 05h 43m 55s
  |+++                                               | 6 % ~02d 05h 13m 21s
  |++++                                              | 7 % ~02d 06h 11m 00s
  |++++                                              | 8 % ~02d 05h 23m 53s
  |+++++                                             | 9 % ~02d 04h 30m 49s
  |+++++                                             | 10% ~02d 04h 11m 26s
  |++++++                                            | 11% ~02d 03h 29m 17s
  |++++++                                            | 12% ~02d 02h 42m 11s
  |+++++++                                           | 13% ~02d 02h 24m 55s
  |+++++++                                           | 14% ~02d 01h 41m 47s
  |++++++++                                          | 15% ~02d 01h 25m 26s
  |++++++++                                          | 16% ~02d 00h 54m 42s
  |+++++++++                                         | 17% ~02d 00h 03m 36s
  |+++++++++                                         | 18% ~01d 23h 29m 41s
  |++++++++++                                        | 19% ~01d 22h 45m 33s
  |++++++++++                                        | 20% ~01d 21h 50m 30s
  |+++++++++++                                       | 21% ~01d 21h 14m 04s
  |+++++++++++                                       | 22% ~01d 20h 34m 58s
  |++++++++++++                                      | 23% ~01d 20h 08m 32s
  |++++++++++++                                      | 24% ~01d 19h 32m 28s
  |+++++++++++++                                     | 25% ~01d 18h 53m 49s
  |+++++++++++++                                     | 26% ~01d 18h 14m 19s
  |++++++++++++++                                    | 27% ~01d 17h 39m 34s
  |++++++++++++++                                    | 28% ~01d 17h 11m 30s
  |+++++++++++++++                                   | 29% ~01d 16h 46m 55s
  |+++++++++++++++                                   | 30% ~01d 16h 16m 34s
  |++++++++++++++++                                  | 31% ~01d 15h 35m 32s
  |++++++++++++++++                                  | 32% ~01d 14h 56m 52s
  |+++++++++++++++++                                 | 33% ~01d 14h 25m 56s
  |+++++++++++++++++                                 | 34% ~01d 13h 51m 05s
  |++++++++++++++++++                                | 35% ~01d 13h 17m 15s
  |++++++++++++++++++                                | 36% ~01d 12h 39m 13s
  |+++++++++++++++++++                               | 37% ~01d 12h 00m 52s
  |+++++++++++++++++++                               | 38% ~01d 11h 35m 20s
  |++++++++++++++++++++                              | 39% ~01d 11h 01m 13s
  |++++++++++++++++++++                              | 40% ~01d 10h 23m 26s
  |+++++++++++++++++++++                             | 41% ~01d 09h 47m 04s
  |+++++++++++++++++++++                             | 42% ~01d 09h 13m 18s
  |++++++++++++++++++++++                            | 43% ~01d 08h 37m 22s
  |++++++++++++++++++++++                            | 44% ~01d 08h 05m 25s
  |+++++++++++++++++++++++                           | 45% ~01d 07h 30m 49s
  |+++++++++++++++++++++++                           | 46% ~01d 06h 52m 44s
  |++++++++++++++++++++++++                          | 47% ~01d 06h 15m 28s
  |++++++++++++++++++++++++                          | 48% ~01d 05h 41m 15s
  |+++++++++++++++++++++++++                         | 49% ~01d 05h 06m 05s
  |+++++++++++++++++++++++++                         | 50% ~01d 04h 36m 22s
  |++++++++++++++++++++++++++                        | 51% ~01d 04h 02m 26s
  |++++++++++++++++++++++++++                        | 52% ~01d 03h 26m 60s
  |+++++++++++++++++++++++++++                       | 53% ~01d 02h 49m 39s
  |+++++++++++++++++++++++++++                       | 54% ~01d 02h 15m 55s
  |++++++++++++++++++++++++++++                      | 55% ~01d 01h 41m 50s
  |++++++++++++++++++++++++++++                      | 56% ~01d 01h 07m 07s
  |+++++++++++++++++++++++++++++                     | 57% ~01d 00h 32m 10s
  |+++++++++++++++++++++++++++++                     | 58% ~23h 56m 19s  
  |++++++++++++++++++++++++++++++                    | 59% ~23h 20m 40s  
  |++++++++++++++++++++++++++++++                    | 60% ~22h 47m 28s  
  |+++++++++++++++++++++++++++++++                   | 61% ~22h 14m 05s  
  |+++++++++++++++++++++++++++++++                   | 62% ~21h 40m 42s  
  |++++++++++++++++++++++++++++++++                  | 63% ~21h 06m 44s  
  |++++++++++++++++++++++++++++++++                  | 64% ~20h 32m 04s  
  |+++++++++++++++++++++++++++++++++                 | 65% ~19h 56m 27s  
  |+++++++++++++++++++++++++++++++++                 | 66% ~19h 22m 30s  
  |++++++++++++++++++++++++++++++++++                | 67% ~18h 48m 31s  
  |++++++++++++++++++++++++++++++++++                | 68% ~18h 14m 06s  
  |+++++++++++++++++++++++++++++++++++               | 69% ~17h 38m 53s  
  |+++++++++++++++++++++++++++++++++++               | 70% ~17h 03m 54s  
  |++++++++++++++++++++++++++++++++++++              | 71% ~16h 30m 03s  
  |++++++++++++++++++++++++++++++++++++              | 72% ~15h 55m 08s  
  |+++++++++++++++++++++++++++++++++++++             | 73% ~15h 21m 54s  
  |+++++++++++++++++++++++++++++++++++++             | 74% ~14h 48m 03s  
  |++++++++++++++++++++++++++++++++++++++            | 75% ~14h 14m 20s  
  |++++++++++++++++++++++++++++++++++++++            | 76% ~13h 39m 42s  
  |+++++++++++++++++++++++++++++++++++++++           | 77% ~13h 06m 04s  
  |+++++++++++++++++++++++++++++++++++++++           | 78% ~12h 32m 35s  
  |++++++++++++++++++++++++++++++++++++++++          | 79% ~11h 58m 06s  
  |++++++++++++++++++++++++++++++++++++++++          | 80% ~11h 23m 56s  
  |+++++++++++++++++++++++++++++++++++++++++         | 81% ~10h 49m 43s  
  |+++++++++++++++++++++++++++++++++++++++++         | 82% ~10h 14m 45s  
  |++++++++++++++++++++++++++++++++++++++++++        | 83% ~09h 41m 07s  
  |++++++++++++++++++++++++++++++++++++++++++        | 84% ~09h 06m 34s  
  |+++++++++++++++++++++++++++++++++++++++++++       | 85% ~08h 32m 37s  
  |+++++++++++++++++++++++++++++++++++++++++++       | 86% ~07h 57m 47s  
  |++++++++++++++++++++++++++++++++++++++++++++      | 87% ~07h 23m 31s  
  |++++++++++++++++++++++++++++++++++++++++++++      | 88% ~06h 49m 05s  
  |+++++++++++++++++++++++++++++++++++++++++++++     | 89% ~06h 14m 29s  
  |+++++++++++++++++++++++++++++++++++++++++++++     | 90% ~05h 40m 14s  
  |++++++++++++++++++++++++++++++++++++++++++++++    | 91% ~05h 06m 20s  
  |++++++++++++++++++++++++++++++++++++++++++++++    | 92% ~04h 32m 23s  
  |+++++++++++++++++++++++++++++++++++++++++++++++   | 93% ~03h 58m 25s  
  |+++++++++++++++++++++++++++++++++++++++++++++++   | 94% ~03h 24m 18s  
  |++++++++++++++++++++++++++++++++++++++++++++++++  | 95% ~02h 50m 07s  
  |++++++++++++++++++++++++++++++++++++++++++++++++  | 96% ~02h 15m 56s  
  |+++++++++++++++++++++++++++++++++++++++++++++++++ | 97% ~01h 42m 00s  
  |+++++++++++++++++++++++++++++++++++++++++++++++++ | 98% ~01h 07m 59s  
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 99% ~33m 59s      
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=02d 08h 37m 33s

  |                                                  | 0 % ~calculating  
  |+                                                 | 1 % ~06s          
  |+                                                 | 2 % ~06s          
  |++                                                | 3 % ~11s          
  |++                                                | 4 % ~09s          
  |+++                                               | 5 % ~08s          
  |+++                                               | 6 % ~07s          
  |++++                                              | 7 % ~08s          
  |++++                                              | 8 % ~07s          
  |+++++                                             | 9 % ~07s          
  |+++++                                             | 10% ~07s          
  |++++++                                            | 11% ~06s          
  |++++++                                            | 12% ~06s          
  |+++++++                                           | 13% ~06s          
  |+++++++                                           | 14% ~06s          
  |++++++++                                          | 15% ~06s          
  |++++++++                                          | 16% ~06s          
  |+++++++++                                         | 17% ~05s          
  |+++++++++                                         | 18% ~05s          
  |++++++++++                                        | 19% ~05s          
  |++++++++++                                        | 20% ~05s          
  |+++++++++++                                       | 21% ~05s          
  |+++++++++++                                       | 22% ~05s          
  |++++++++++++                                      | 23% ~05s          
  |++++++++++++                                      | 24% ~05s          
  |+++++++++++++                                     | 25% ~05s          
  |+++++++++++++                                     | 26% ~05s          
  |++++++++++++++                                    | 27% ~05s          
  |++++++++++++++                                    | 28% ~04s          
  |+++++++++++++++                                   | 29% ~04s          
  |+++++++++++++++                                   | 30% ~04s          
  |++++++++++++++++                                  | 31% ~04s          
  |++++++++++++++++                                  | 32% ~04s          
  |+++++++++++++++++                                 | 33% ~04s          
  |+++++++++++++++++                                 | 34% ~04s          
  |++++++++++++++++++                                | 35% ~04s          
  |++++++++++++++++++                                | 36% ~04s          
  |+++++++++++++++++++                               | 37% ~04s          
  |+++++++++++++++++++                               | 38% ~04s          
  |++++++++++++++++++++                              | 39% ~04s          
  |++++++++++++++++++++                              | 40% ~04s          
  |+++++++++++++++++++++                             | 41% ~04s          
  |+++++++++++++++++++++                             | 42% ~03s          
  |++++++++++++++++++++++                            | 43% ~03s          
  |++++++++++++++++++++++                            | 44% ~03s          
  |+++++++++++++++++++++++                           | 45% ~03s          
  |+++++++++++++++++++++++                           | 46% ~03s          
  |++++++++++++++++++++++++                          | 47% ~03s          
  |++++++++++++++++++++++++                          | 48% ~03s          
  |+++++++++++++++++++++++++                         | 49% ~03s          
  |+++++++++++++++++++++++++                         | 50% ~03s          
  |++++++++++++++++++++++++++                        | 51% ~03s          
  |++++++++++++++++++++++++++                        | 52% ~03s          
  |+++++++++++++++++++++++++++                       | 53% ~03s          
  |+++++++++++++++++++++++++++                       | 54% ~03s          
  |++++++++++++++++++++++++++++                      | 55% ~03s          
  |++++++++++++++++++++++++++++                      | 56% ~03s          
  |+++++++++++++++++++++++++++++                     | 57% ~03s          
  |+++++++++++++++++++++++++++++                     | 58% ~03s          
  |++++++++++++++++++++++++++++++                    | 59% ~02s          
  |++++++++++++++++++++++++++++++                    | 60% ~02s          
  |+++++++++++++++++++++++++++++++                   | 61% ~02s          
  |+++++++++++++++++++++++++++++++                   | 62% ~02s          
  |++++++++++++++++++++++++++++++++                  | 63% ~02s          
  |++++++++++++++++++++++++++++++++                  | 64% ~02s          
  |+++++++++++++++++++++++++++++++++                 | 65% ~02s          
  |+++++++++++++++++++++++++++++++++                 | 66% ~02s          
  |++++++++++++++++++++++++++++++++++                | 67% ~02s          
  |++++++++++++++++++++++++++++++++++                | 68% ~02s          
  |+++++++++++++++++++++++++++++++++++               | 69% ~02s          
  |+++++++++++++++++++++++++++++++++++               | 70% ~02s          
  |++++++++++++++++++++++++++++++++++++              | 71% ~02s          
  |++++++++++++++++++++++++++++++++++++              | 72% ~02s          
  |+++++++++++++++++++++++++++++++++++++             | 73% ~02s          
  |+++++++++++++++++++++++++++++++++++++             | 74% ~02s          
  |++++++++++++++++++++++++++++++++++++++            | 75% ~01s          
  |++++++++++++++++++++++++++++++++++++++            | 76% ~01s          
  |+++++++++++++++++++++++++++++++++++++++           | 77% ~01s          
  |+++++++++++++++++++++++++++++++++++++++           | 78% ~01s          
  |++++++++++++++++++++++++++++++++++++++++          | 79% ~01s          
  |++++++++++++++++++++++++++++++++++++++++          | 80% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++         | 81% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++         | 82% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++        | 83% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++        | 84% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++++       | 85% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++++       | 86% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++++      | 87% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++++      | 88% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++++++     | 89% ~01s          
  |+++++++++++++++++++++++++++++++++++++++++++++     | 90% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++++++    | 91% ~01s          
  |++++++++++++++++++++++++++++++++++++++++++++++    | 92% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++   | 93% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++   | 94% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++  | 95% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++  | 96% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++++ | 97% ~00s          
  |+++++++++++++++++++++++++++++++++++++++++++++++++ | 98% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 99% ~00s          
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=06s  
gcql1 <- ScoreJackStraw(gcql1, dims = 1:200)
JackStrawPlot(gcql1, dims = 1:100)

ElbowPlot(object = gcql1,ndims=100)

pc.num<-46

Based on the JackStraw plot and the Elbow plot, we decided to use the top 46 PCs for the unimputed data.

saveRDS(gcql1, file = file.path(output.dir,paste("2_dimreduction_data_",length(gcql1@assays$RNA@var.features),"vargenes_pipeline1.rds",sep="")))
# we save the file as preprocessed_data_saver.rds to prevent any mistakes but we change the file name back to preprocessed_data.rds before performing downstream analysis.

cat("We save the R project as\n")
We save the R project as
cat("unimputed data:\t",file.path(output.dir,paste("2_dimreduction_data_",length(gcql1@assays$RNA@var.features),"vargenes_pipeline1.rds",sep="")),"\n",sep="")
unimputed data: /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/2_dimreduction_data_2000vargenes_pipeline1.rds

Data visualization

UMAP

To identify potential outlying cells and samples, we first label the tSNE plot using disease_samplenames.

sample1<-FindNeighbors(gcql1,dims=1:pc.num)
Computing nearest neighbor graph
Computing SNN
sample1<-FindClusters(sample1,resolution=1.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 70244
Number of edges: 2398867

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8762
Number of communities: 40
Elapsed time: 23 seconds
sample1<-RunUMAP(sample1,dims=1:pc.num)
22:23:44 UMAP embedding parameters a = 0.9922 b = 1.112
22:23:44 Read 70244 rows and found 46 numeric columns
22:23:44 Using Annoy for neighbor search, n_neighbors = 30
22:23:44 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
22:23:59 Writing NN index file to temp file /tmp/RtmpGbMf9o/file31239293ae5
22:23:59 Searching Annoy index using 1 thread, search_k = 3000
22:24:35 Annoy recall = 100%
22:24:37 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
22:24:42 Initializing from normalized Laplacian + noise (using irlba)
22:24:49 Commencing optimization for 200 epochs, with 3281762 positive edges
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
22:26:36 Optimization finished
#sample1<-RunUMAP(sample1,dims=1:pc.num,umap.method="umap-learn")
fig1<-DimPlot(sample1,reduction="umap")
fig2<-DimPlot(sample1,reduction="umap",group.by="disease.samplenames")
fig3<-DimPlot(sample1,reduction="umap",group.by="disease")
grid.arrange(fig1,fig2,fig3,ncol=1)

pc.num<-46
# generate the TSNE plot labeled by labeling samples with different colors 
sample1<-FindNeighbors(gcql1,dims=1:pc.num)
sample1<-FindClusters(sample1,resolution=1.2)
sample1<-RunUMAP(sample1,dims=1:pc.num)
#sample1<-RunUMAP(sample1,dims=1:pc.num,umap.method="umap-learn")
fig1<-DimPlot(sample1,reduction="umap")
fig2<-DimPlot(sample1,reduction="umap",group.by="disease.samplenames")
fig3<-DimPlot(sample1,reduction="umap",group.by="disease")
grid.arrange(fig1,fig2,fig3,ncol=1)

tSNE plot

pc.num<-35
sample1 <- RunTSNE(sample1, dims= 1:pc.num)

data saving

saveRDS(sample1, file = file.path(output.dir,paste("2_visualization_data_",length(gcql1@assays$RNA@var.features),"vargenes_pipeline1.rds",sep="")))
cat("We saved the seurat object with UMAP and tSNE as ",file.path(output.dir,paste("2_visualization_data_",length(sample1@assays$RNA@var.features),"vargenes_pipeline1.rds",sep="")),"\n",sep="")
We saved the seurat object with UMAP and tSNE as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/2_visualization_data_2000vargenes_pipeline1.rds

Cell Typing

my.distinct.colors<-c('#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000')

To describe the cell types captured in the data, the integrated analysis may provide cleaner results. Therefore, we conduct the integrated analysis of all the asthma 10X data using Seurat V3.1 in this note to reduce the subject effect in the data visulization and the trajectory analysis at least.

Data Loading

########################################################
# 1. load in the singler object
data.dir<-file.path(home.dir,"scRNA-seq/Seurat")
sample1<-readRDS(file.path(data.dir,"2_visualization_data_2000vargenes_pipeline1.rds"),refhook = NULL)

sample1.raw<-sample1

integrating data

Split the whole dataset based on subject ID (fresh and DSMO samples from the same subject are considered as one sample). Find anchors to integrate all the datasets together.

sample1.list<-SplitObject(sample1.raw,split.by="sample.names")
sample1.list<-lapply(X=sample1.list,FUN=function(x){
  x<-NormalizeData(x)
  x<-FindVariableFeatures(x,selection.method="vst",nfeatures=2000)
})
temp.cellnum<-unlist(lapply(sample1.list,ncol))
#sample1.list<-sample1.list[temp.cellnum>=40]
# increase the global maxSize
options(future.globals.maxSize= 4194304000)
features <- SelectIntegrationFeatures(object.list = sample1.list)
#sample1.list <- lapply(X = sample1.list, FUN = function(x) {
#    x <- ScaleData(x, features = features, verbose = FALSE)
#    x <- RunPCA(x, features =features, npcs=40,verbose=F)
#})
#sample1.anchors<-FindIntegrationAnchors(sample1.list[temp.cellnum>=92],k.filter=92,dims=1:40,verbose=FALSE)
sample1.anchors<-FindIntegrationAnchors(object.list=sample1.list[temp.cellnum>=700],anchor.features=features, reduction="rpca",k.filter=40,dims=1:40,verbose=FALSE)
#sample1.combined<-IntegrateData(anchorset=sample1.anchors,features.to.integrate=rownames(sample1.raw@assays$RNA@counts),dims=1:40,verbose=FALSE)
#,k.filter=40,dims=1:40
sample1.combined<-IntegrateData(anchorset=sample1.anchors,verbose=FALSE,k.weight = 45)
output.dir<-file.path(home.dir,"scRNA-seq/Seurat")
dir.create(file.path(output.dir,"integrated_analysis"))
output.filepath<-file.path(output.dir,"integrated_analysis","seurat_object_integrated_allcells.rds")
saveRDS(sample1.combined,file=output.filepath)
cat("We saved the integrated data for all cells as ",output.filepath,"\n",sep="")
We saved the integrated data for all cells as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/integrated_analysis/seurat_object_integrated_allcells.rds
output.filepath<-file.path(output.dir,"integrated_analysis","seurat_object_original_allcells.rds")
saveRDS(sample1.raw,file=output.filepath)
cat("We saved the original data for all cells as ",output.filepath,"\n",sep="")
We saved the original data for all cells as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/integrated_analysis/seurat_object_original_allcells.rds

Cluster the cells using the integrated data.

DefaultAssay(sample1.combined) <- "integrated"
# Run the standard workflow for visualization and clustering
sample1.combined <- ScaleData(sample1.combined, verbose = FALSE)
sample1.combined <- RunPCA(sample1.combined, npcs = 40, verbose = FALSE)
# t-SNE and Clustering
sample1.combined <- RunUMAP(sample1.combined, reduction = "pca", dims = 1:40)
Warning: The default method for RunUMAP has changed from calling Python UMAP via reticulate to the R-native UWOT using the cosine metric
To use Python UMAP via reticulate, set umap.method to 'umap-learn' and metric to 'correlation'
This message will be shown once per session
12:26:39 UMAP embedding parameters a = 0.9922 b = 1.112
12:26:40 Read 69994 rows and found 40 numeric columns
12:26:40 Using Annoy for neighbor search, n_neighbors = 30
12:26:40 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
12:27:10 Writing NN index file to temp file /tmp/RtmpRkkNQ1/file738831a9bb4e
12:27:10 Searching Annoy index using 1 thread, search_k = 3000
12:27:54 Annoy recall = 100%
12:27:57 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
12:28:15 Initializing from normalized Laplacian + noise (using irlba)
12:28:45 Commencing optimization for 200 epochs, with 3621056 positive edges
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
12:30:39 Optimization finished
sample1.combined <- FindNeighbors(sample1.combined, reduction = "pca", dims = 1:40)
Computing nearest neighbor graph
Computing SNN
sample1.combined <- FindClusters(sample1.combined, resolution = 1.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 69994
Number of edges: 4351747

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8898
Number of communities: 39
Elapsed time: 29 seconds
1 singletons identified. 38 final clusters.

We save the result in a rds file.

output.subdir<-file.path(output.dir,"integrated_analysis")
if(file.exists(output.subdir)==F){
  dir.create(output.subdir)
}

output.filepath<-file.path(output.subdir,"seurat_object_integrated_allcells_cellclustering_noSingleR.rds")
saveRDS(sample1.combined,file=output.filepath)
cat("We saved the seurat object of the integrated data with cell clustering results only as ",output.filepath,"\n",sep="")
We saved the seurat object of the integrated data with cell clustering results only as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/integrated_analysis/seurat_object_integrated_allcells_cellclustering_noSingleR.rds

Visualizing integrated data

library(cowplot)
p1 <- DimPlot(sample1.combined, reduction = "umap", group.by = "sample.names")
p2 <- DimPlot(sample1.combined, reduction = "umap", label = TRUE)
#p3<-DimPlot(sample1.combined, reduction = "umap", group.by="singler.hpca.cluster.merged", label = TRUE,cols=my.distinct.colors)
#p4<-DimPlot(sample1.combined, reduction = "umap", group.by="singler.hpca.cluster", label = TRUE,cols=my.distinct.colors)
p5<-DimPlot(sample1.combined, reduction = "umap", group.by="disease", label = TRUE,cols=my.distinct.colors)
#plot_grid(p2, p1, p3,p4,p5,ncol=1)
plot_grid(p2, p1,p5,ncol=1)

cat("The UMAP of each sample highlighted has been saved as ",output.filepath,"\n",sep="")
The UMAP of each sample highlighted has been saved as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/umap_integrated_courtney_persample.pdf

Integrate using Azimuth

We uploaded the seurat_object_original_allcells.rds onto Azimuth website and map them to the reference human PBMC data. We downloaded the predicted cell types to compare to the predictions by Amy’s data.

# these are codes downloaded from the Azimuth website as analysis.R codes
#!/usr/bin/env Rscript

# Ensure Seurat v4.0 or higher is installed
if (packageVersion(pkg = "Seurat") < package_version(x = "4.0.0")) {
  stop("Mapping datasets requires Seurat v4 or higher.", call. = FALSE)
}

# Ensure glmGamPoi is installed
if (!requireNamespace("glmGamPoi", quietly = TRUE)) {
  if (!requireNamespace("BiocManager", quietly = TRUE)) {
    BiocManager::install("glmGamPoi")
  }
}

# Ensure Azimuth is installed
if (packageVersion(pkg = "Azimuth") < package_version(x = "0.3.1")) {
  stop("Please install azimuth - remotes::install_github('satijalab/azimuth')", call. = FALSE)
}

library(Seurat)
library(Azimuth)

# Download the Azimuth reference and extract the archive

# Load the reference
# Change the file path based on where the reference is located on your system.
reference <- LoadReference(path = "https://seurat.nygenome.org/azimuth/references/v1.0.0/human_pbmc")

# Load the query object for mapping
# Change the file path based on where the query file is located on your system.
query <- LoadFileInput(path = "seurat_object_original_allcells.rds")

# Calculate nCount_RNA and nFeature_RNA if the query does not
# contain them already
if (!all(c("nCount_RNA", "nFeature_RNA") %in% c(colnames(x = query[[]])))) {
    calcn <- as.data.frame(x = Seurat:::CalcN(object = query))
    colnames(x = calcn) <- paste(
      colnames(x = calcn),
      "RNA",
      sep = '_'
    )
    query <- AddMetaData(
      object = query,
      metadata = calcn
    )
    rm(calcn)
}

# Calculate percent mitochondrial genes if the query contains genes
# matching the regular expression "^MT-"
if (any(grepl(pattern = '^MT-', x = rownames(x = query)))) {
  query <- PercentageFeatureSet(
    object = query,
    pattern = '^MT-',
    col.name = 'percent.mt',
    assay = "RNA"
  )
}

# Filter cells based on the thresholds for nCount_RNA and nFeature_RNA
# you set in the app
cells.use <- query[["nCount_RNA", drop = TRUE]] <= 67269 &
  query[["nCount_RNA", drop = TRUE]] >= 457 &
  query[["nFeature_RNA", drop = TRUE]] <= 7761 &
  query[["nFeature_RNA", drop = TRUE]] >= 300

# If the query contains mitochondrial genes, filter cells based on the
# thresholds for percent.mt you set in the app
if ("percent.mt" %in% c(colnames(x = query[[]]))) {
  cells.use <- cells.use & (query[["percent.mt", drop = TRUE]] <= 100 &
    query[["percent.mt", drop = TRUE]] >= 0)
}

# Remove filtered cells from the query
query <- query[, cells.use]

# Preprocess with SCTransform
query <- SCTransform(
  object = query,
  assay = "RNA",
  new.assay.name = "refAssay",
  residual.features = rownames(x = reference$map),
  reference.SCT.model = reference$map[["refAssay"]]@SCTModel.list$refmodel,
  method = 'glmGamPoi',
  ncells = 2000,
  n_genes = 2000,
  do.correct.umi = FALSE,
  do.scale = FALSE,
  do.center = TRUE
)

# Find anchors between query and reference
anchors <- FindTransferAnchors(
  reference = reference$map,
  query = query,
  k.filter = NA,
  reference.neighbors = "refdr.annoy.neighbors",
  reference.assay = "refAssay",
  query.assay = "refAssay",
  reference.reduction = "refDR",
  normalization.method = "SCT",
  features = intersect(rownames(x = reference$map), VariableFeatures(object = query)),
  dims = 1:50,
  n.trees = 20,
  mapping.score.k = 100
)

# Transfer cell type labels and impute protein expression
#
# Transferred labels are in metadata columns named "predicted.*"
# The maximum prediction score is in a metadata column named "predicted.*.score"
# The prediction scores for each class are in an assay named "prediction.score.*"
# The imputed assay is named "impADT" if computed

refdata <- lapply(X = "celltype.l2", function(x) {
  reference$map[[x, drop = TRUE]]
})
names(x = refdata) <- "celltype.l2"
if (TRUE) {
  refdata[["impADT"]] <- GetAssayData(
    object = reference$map[['ADT']],
    slot = 'data'
  )
}
query <- TransferData(
  reference = reference$map,
  query = query,
  dims = 1:50,
  anchorset = anchors,
  refdata = refdata,
  n.trees = 20,
  store.weights = TRUE
)

# Calculate the embeddings of the query data on the reference SPCA
query <- IntegrateEmbeddings(
  anchorset = anchors,
  reference = reference$map,
  query = query,
  reductions = "pcaproject",
  reuse.weights.matrix = TRUE
)

# Calculate the query neighbors in the reference
# with respect to the integrated embeddings
query[["query_ref.nn"]] <- FindNeighbors(
  object = Embeddings(reference$map[["refDR"]]),
  query = Embeddings(query[["integrated_dr"]]),
  return.neighbor = TRUE,
  l2.norm = TRUE
)

# The reference used in the app is downsampled compared to the reference on which
# the UMAP model was computed. This step, using the helper function NNTransform,
# corrects the Neighbors to account for the downsampling.
query <- NNTransform(
  object = query,
  meta.data = reference$map[[]]
)

# Project the query to the reference UMAP.
query[["proj.umap"]] <- RunUMAP(
  object = query[["query_ref.nn"]],
  reduction.model = reference$map[["refUMAP"]],
  reduction.key = 'UMAP_'
)


# Calculate mapping score and add to metadata
query <- AddMetaData(
  object = query,
  metadata = MappingScore(anchors = anchors),
  col.name = "mapping.score"
)

# VISUALIZATIONS

# First predicted metadata field, change to visualize other predicted metadata
id <- "celltype.l2"[1]
predicted.id <- paste0("predicted.", id)

# DimPlot of the reference
DimPlot(object = reference$plot, reduction = "refUMAP", group.by = id, label = TRUE) + NoLegend()

# DimPlot of the query, colored by predicted cell type
DimPlot(object = query, reduction = "proj.umap", group.by = predicted.id, label = TRUE) + NoLegend()

# Plot the score for the predicted cell type of the query
FeaturePlot(object = query, features = paste0(predicted.id, ".score"), reduction = "proj.umap")
VlnPlot(object = query, features = paste0(predicted.id, ".score"), group.by = predicted.id) + NoLegend()

# Plot the mapping score
FeaturePlot(object = query, features = "mapping.score", reduction = "proj.umap")
VlnPlot(object = query, features = "mapping.score", group.by = predicted.id) + NoLegend()

# Plot the prediction score for the class CD16 Mono
FeaturePlot(object = query, features = "CD16 Mono", reduction = "proj.umap")
VlnPlot(object = query, features = "CD16 Mono", group.by = predicted.id) + NoLegend()

# Plot an RNA feature
FeaturePlot(object = query, features = "GNLY", reduction = "proj.umap")
VlnPlot(object = query, features = "GNLY", group.by = predicted.id) + NoLegend()

# Plot an imputed protein feature
if (TRUE) {
  FeaturePlot(object = query, features = "CD3-1", reduction = "proj.umap")
  VlnPlot(object = query, features = "CD3-1", group.by = predicted.id) + NoLegend()
}

We load in the predicted cell types by Azimuth so that it can be compared to the annotations based on Amy’s data.

# load in the predicted cell types by Azimuth
azimuth.filepath<-"/home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/azimuth/azimuth_pred.tsv"
azimuth.pred<-read_tsv(azimuth.filepath)
Rows: 70244 Columns: 4
── Column specification ──────────────────────────────────────────────────────────
Delimiter: "\t"
chr (2): cell, predicted.celltype.l2
dbl (2): predicted.celltype.l2.score, mapping.score

ℹ Use `spec()` to retrieve the full column specification for this data.
ℹ Specify the column types or set `show_col_types = FALSE` to quiet this message.
azimuth.pred<-as.data.frame(azimuth.pred)
rownames(azimuth.pred)<-as.character(azimuth.pred[,1])

Integrate with Amy’s data

We obtained another PBMC 10X data annotated in Kaminski lab so that we can integrate this dataset with the annotated data to annotate the cells in this dataset. The seurat object containing the annotated PBMC 10x data is under /home/xy48/scratch_palmer/Amy_PBMC on grace hpc. We ran this section of codes on hpc.

data loading

We first load in the annotated PBMC 10x data from Amy.

my.distinct.colors<-c('#e6194b', '#3cb44b', '#ffe119', '#4363d8', '#f58231', '#911eb4', '#46f0f0', '#f032e6', '#bcf60c', '#fabebe', '#008080', '#e6beff', '#9a6324', '#fffac8', '#800000', '#aaffc3', '#808000', '#ffd8b1', '#000075', '#808080', '#ffffff', '#000000')

data.filepath<-"/home/yanxiting/driver_Grace/scratch_palmer/Amy_PBMC/01.03.23.controls.for.Xiting.rds"
amy.10x<-readRDS(data.filepath,refhook = NULL)
amy.10x<-subset(amy.10x,cells= rownames(amy.10x@meta.data)[!is.na(amy.10x@meta.data$SampleID)])
temp.num<-as.matrix(table(amy.10x@meta.data$SampleID))[,1]
amy.10x<-subset(amy.10x,cells=rownames(amy.10x@meta.data)[amy.10x@meta.data$SampleID%in%names(temp.num)[temp.num>=700]])

DefaultAssay(amy.10x)<-"integrated"
DimPlot(amy.10x, reduction = "umap",group.by="Annotation_090822",cols = my.distinct.colors)

DimPlot(amy.10x, reduction = "umap",group.by="SampleID",cols = my.distinct.colors)

We then load in the integrated seurat object of our data.

data.filepath<-"/home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/integrated_analysis/seurat_object_integrated_allcells_cellclustering_noSingleR.rds"
sample1.combined<-readRDS(data.filepath,refhook=NULL)
# focus on samples with >=700 cells in total
temp.num<-as.matrix(table(sample1.combined@meta.data$sample.names))[,1]
sample1.combined<-subset(sample1.combined,cells=rownames(sample1.combined@meta.data)[sample1.combined@meta.data$sample.names%in%names(temp.num)[temp.num>=700]])
Merge counts

Merge the raw count data from these two sources for integration analysis.

temp.names<-intersect(rownames(sample1.combined@assays$RNA@counts),rownames(amy.10x@assays$RNA@counts))
merged.counts<-cbind(sample1.combined@assays$RNA@counts[temp.names,],amy.10x@assays$RNA@counts[temp.names,])
merged.metadata<-rbind(as.matrix(sample1.combined@meta.data[,c("disease","sample.names")]),as.matrix(amy.10x@meta.data[,c("disease","sampleID")]))
temp<-c(rep(NA,nrow(sample1.combined@meta.data)),amy.10x@meta.data$Annotation_090822)
merged.metadata<-cbind(merged.metadata,temp)
colnames(merged.metadata)[3]<-"Annotation_090822"
temp.pred<-c(rep(NA,nrow(sample1.combined@meta.data)),rep(NA,nrow(amy.10x@meta.data)))
names(temp.pred)<-c(rownames(sample1.combined@meta.data),rownames(amy.10x@meta.data))
temp.names<-intersect(rownames(sample1.combined@meta.data),rownames(azimuth.pred))
temp.pred[temp.names]<-azimuth.pred[temp.names,"predicted.celltype.l2"]

merged.metadata<-cbind(merged.metadata,temp.pred)
colnames(merged.metadata)[4]<-"Annotation_azimuth"

merged.data<-CreateSeuratObject(counts=merged.counts,project="MergedSeuratObject",assay="RNA",min.cells=3,min.features = 200)
merged.data[["percent.mt"]] <- PercentageFeatureSet(merged.data, pattern = "^MT-")

cat("\n")
cat("Originally, there are ",nrow(merged.counts)," genes and ",ncol(merged.counts)," cells in the data.\n",sep="")
Originally, there are 12834 genes and 116064 cells in the data.
cat("After the first step filtering, there are ",nrow(merged.data@assays$RNA@data)," genes and ",ncol(merged.data@assays$RNA@data)," cells in the filtered data.\n",sep="")
After the first step filtering, there are 12834 genes and 116059 cells in the filtered data.
rm(merged.counts)
#gc(verbose=F)
invisible(gc())


merged.data <- AddMetaData(object = merged.data, metadata = merged.metadata[,"disease"], col.name = "disease")
merged.data <- AddMetaData(object = merged.data, metadata = merged.metadata[,"sample.names"], col.name = "sample.names")
merged.data <- AddMetaData(object = merged.data, metadata = merged.metadata[,"Annotation_090822"], col.name = "Annotation_090822")
merged.data <- AddMetaData(object = merged.data, metadata = merged.metadata[,"Annotation_azimuth"], col.name = "Annotation_azimuth")
temp<-rep("",nrow(merged.data@meta.data))
temp[substr(merged.data@meta.data$sample.names,1,1)=="G"]<-"courtney"
temp[substr(merged.data@meta.data$sample.names,1,1)=="C"]<-"amy"
merged.data <- AddMetaData(object = merged.data, metadata = temp, col.name = "study")
integration analysis

Split the whole dataset based on subject ID. Find anchors to integrate all the datasets together.

sample1.raw<-merged.data
sample1.list<-SplitObject(sample1.raw,split.by="study")
#sample1.list<-SplitObject(sample1.raw,split.by="sample.names")
sample1.list<-lapply(X=sample1.list,FUN=function(x){
  x<-NormalizeData(x)
  x<-FindVariableFeatures(x,selection.method="vst",nfeatures=2000)
})
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Performing log-normalization
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating gene variances
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Calculating feature variances of standardized and clipped values
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
temp.cellnum<-unlist(lapply(sample1.list,ncol))

# increase the global maxSize
options(future.globals.maxSize= 40194304000)
features <- SelectIntegrationFeatures(object.list = sample1.list)
sample1.list <- lapply(X = sample1.list, FUN = function(x) {
    x <- ScaleData(x, features = features, verbose = FALSE)
    x <- RunPCA(x, features =features, npcs=40,verbose=F)
})
#sample1.anchors<-FindIntegrationAnchors(sample1.list[temp.cellnum>=92],k.filter=92,dims=1:40,verbose=FALSE)
sample1.anchors<-FindIntegrationAnchors(object.list=sample1.list,anchor.features=features, reduction="rpca",verbose=FALSE)

  |                                                  | 0 % ~calculating  
  |++++++++++++++++++++++++++++++++++++++++++++++++++| 100% elapsed=01m 20s
# reference=c(1,2): https://satijalab.org/seurat/archive/v3.2/integration.html
#sample1.combined<-IntegrateData(anchorset=sample1.anchors,features.to.integrate=rownames(sample1.raw@assays$RNA@counts),dims=1:40,verbose=FALSE)
#,k.filter=40,dims=1:40
merged.combined<-IntegrateData(anchorset=sample1.anchors,verbose=FALSE)
cell clustering

Cluster the cells using the integrated data.

DefaultAssay(merged.combined) <- "integrated"
# Run the standard workflow for visualization and clustering
merged.combined <- ScaleData(merged.combined, verbose = FALSE)
merged.combined <- RunPCA(merged.combined, npcs = 40, verbose = FALSE)
# t-SNE and Clustering
merged.combined <- RunUMAP(merged.combined, reduction = "pca", dims = 1:40)
22:09:17 UMAP embedding parameters a = 0.9922 b = 1.112
22:09:17 Read 116059 rows and found 40 numeric columns
22:09:17 Using Annoy for neighbor search, n_neighbors = 30
22:09:17 Building Annoy index with metric = cosine, n_trees = 50
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
22:09:44 Writing NN index file to temp file /tmp/RtmpTmLNFP/file1cc5bf8bad1
22:09:44 Searching Annoy index using 1 thread, search_k = 3000
22:10:47 Annoy recall = 100%
22:10:48 Commencing smooth kNN distance calibration using 1 thread with target n_neighbors = 30
22:10:57 Initializing from normalized Laplacian + noise (using irlba)
22:11:11 Commencing optimization for 200 epochs, with 5382232 positive edges
Using method 'umap'
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
22:14:14 Optimization finished
merged.combined <- FindNeighbors(merged.combined, reduction = "pca", dims = 1:40)
Computing nearest neighbor graph
Computing SNN
merged.combined <- FindClusters(merged.combined, resolution = 1.2)
Modularity Optimizer version 1.3.0 by Ludo Waltman and Nees Jan van Eck

Number of nodes: 116059
Number of edges: 3723363

Running Louvain algorithm...
0%   10   20   30   40   50   60   70   80   90   100%
[----|----|----|----|----|----|----|----|----|----|
**************************************************|
Maximum modularity in 10 random starts: 0.8855
Number of communities: 39
Elapsed time: 49 seconds
2 singletons identified. 37 final clusters.
data saving

We save the result in a rds file.

home.dir<-"/home/yanxiting/Documents/Research/GRADS_SARC_PBMC"
output.dir<-file.path(home.dir,"scRNA-seq/Seurat")
temp<-file.path(output.dir,"integrated_amy")
if(file.exists(temp)==F){
  dir.create(temp)
}
output.filepath<-file.path(output.dir,"integrated_amy","seurat_object_integrated_merged_study.rds")
saveRDS(merged.combined,file=output.filepath)
cat("We saved the integrated data based on studies for all cells as ",output.filepath,"\n",sep="")
We saved the integrated data based on studies for all cells as /home/yanxiting/Documents/Research/GRADS_SARC_PBMC/scRNA-seq/Seurat/integrated_amy/seurat_object_integrated_merged_study.rds
output.filepath<-file.path(output.dir,"integrated_amy","seurat_object_original_merged_study.rds")
saveRDS(merged.data,file=output.filepath)
visualization

Generate UMAPs of the Courtney and Amy datasets to compare the cell types predicted by Azimuth and by Amy.

#old Visualize the integrated data by splitting it to different clusters.

fig.list<-list()
celltype.names<-as.numeric(unique(as.character(Idents(sample1.combined))))
celltype.names<-sort(celltype.names,decreasing=F)
for(i in 1:length(celltype.names)){
  temp.map<-as.character(Idents(sample1.combined))
  temp.map[temp.map!=celltype.names[i]]="other"
  sample1.combined@meta.data$temp.map<-temp.map
  fig.list[[2*(i-1)+1]]<-DimPlot(sample1.combined, reduction = "umap", label = FALSE,group.by="ident")
  #fig.list[[2*i]]<-DimPlot(sample1.combined, reduction = "umap", label = FALSE,group.by="temp.map",cols=c(my.distinct.colors[i],"grey"))
  fig.list[[2*i]]<-DimPlot(sample1.combined, reduction = "umap", label = FALSE,group.by="temp.map",cols=c("red","grey"))
}
do.call(grid.arrange,c(fig.list,ncol=2))

Visualize the integrated data by splitting it to different subjects and different cell type annotation (old).

fig.list<-list()
celltype.names<-unique(as.character(sample1.combined@meta.data$singler.hpca.cluster))
celltype.names<-sort(celltype.names,decreasing=T)
for(i in 1:length(celltype.names)){
  temp.map<-as.character(sample1.combined@meta.data$singler.hpca.cluster)
  temp.map[temp.map!=celltype.names[i]]<-"zother"
  sample1.combined@meta.data$temp.map<-temp.map
  fig.list[[i]]<-DimPlot(sample1.combined, reduction = "umap",group.by="temp.map",label = FALSE,pt.size=0.5,cols=c(my.distinct.colors[i],"grey"))
}
do.call(grid.arrange,c(fig.list,ncol=2))

Cell typing using SingleR annotation

We run SingleR (v 1.2.0) again based on the clustering results based on the integrated data and visualize the data using the new cell typing annotation.

# run SingleR with HPCA as reference dataset.
library(SingleR)
library(scRNAseq)
library(scater)
hpca.se <- HumanPrimaryCellAtlasData()
hpca.se
#hESCs <- LaMannoBrainData('human-es')
my.temp<-CreateSeuratObject(counts=sample1.combined@assays$RNA@counts,project = "ChuppSputum",min.cells = 0,  min.features = 0)
temp<-as.SingleCellExperiment(my.temp,assay="RNA")
temp<-logNormCounts(temp)
sample1.singler.hpca<-SingleR(test=temp,ref=hpca.se,labels=hpca.se$label.fine,method="cluster",clusters=as.character(Idents(sample1.combined)))
plotScoreHeatmap(sample1.singler.hpca)

Data visulization labelled by the new SingleR results on the combined data.

cluster.names<-rownames(sample1.singler.hpca)
cluster.labels<-sample1.singler.hpca$pruned.labels
temp.map<-as.character(Idents(sample1.combined))
temp.map<-plyr::mapvalues(x=temp.map,from=cluster.names,to=cluster.labels)
sample1.combined<-AddMetaData(sample1.combined,metadata=temp.map,col.name="singler.hpca.cluster.combined")

Get the main cell type labels and save the annotated seurat R object.

# get the main labels
my.temp<-CreateSeuratObject(counts=sample1.combined@assays$RNA@counts,project = "ChuppSputum",min.cells = 0,  min.features = 0)
temp<-as.SingleCellExperiment(my.temp,assay="RNA")
temp<-logNormCounts(temp)
sample1.singler.hpca<-SingleR(test=temp,ref=hpca.se,labels=hpca.se$label.main,method="cluster",clusters=as.character(Idents(sample1.combined)))
plotScoreHeatmap(sample1.singler.hpca)
cluster.names<-rownames(sample1.singler.hpca)
cluster.labels<-sample1.singler.hpca$pruned.labels
temp.map<-as.character(Idents(sample1.combined))
temp.map<-plyr::mapvalues(x=temp.map,from=cluster.names,to=cluster.labels)
sample1.combined<-AddMetaData(sample1.combined,metadata=temp.map,col.name="singler.hpca.cluster.main.combined")
output.filepath<-file.path(output.subdir,"seurat_object_integrated_allcells_cellclustering_singler_hpca.rds")
saveRDS(sample1.combined,file=output.filepath)

Visulizing the SingleR cell typing results on the combined UMAP.

fig.list<-list()
celltype.names<-unique(as.character(sample1.combined@meta.data$singler.hpca.cluster.combined))
celltype.names<-sort(celltype.names,decreasing=T)
for(i in 1:length(celltype.names)){
  temp.map<-as.character(sample1.combined@meta.data$singler.hpca.cluster.combined)
  temp.map[temp.map!=celltype.names[i]]<-"zother"
  sample1.combined@meta.data$temp.map<-temp.map
  fig.list[[i]]<-DimPlot(sample1.combined, reduction = "umap",group.by="temp.map",label = FALSE,pt.size=0.5,cols=c(my.distinct.colors[i],"grey"))
}
do.call(grid.arrange,c(fig.list,ncol=2))
LS0tCnRpdGxlOiAiR1NFQSBhbmFseXNpcyBvZiBTQVJDIGFzc29jaWF0ZWQgZ2VuZXMiCmF1dGhvcjogIlhpdGluZyBZYW4iCmRhdGU6ICIxMC8wMi8yMDE5IgpvdXRwdXQ6CiAgaHRtbF9ub3RlYm9vazoKICAgIGNvZGVfZm9sZGluZzogaGlkZQogICAgZmlnX2NhcHRpb246IHllcwogICAgaGlnaGxpZ2h0OiB0YW5nbwogICAgbnVtYmVyX3NlY3Rpb25zOiBubwogICAgdGhlbWU6IHVuaXRlZAogICAgdG9jOiB5ZXMKICAgIHRvY19kZXB0aDogNgogICAgdG9jX2Zsb2F0OiB5ZXMKLS0tCgpgYGB7ciBzZXR1cCwgaW5jbHVkZT1GQUxTRX0Ka25pdHI6Om9wdHNfY2h1bmskc2V0KGV2YWw9VFJVRSxlY2hvID0gVFJVRSxjYWNoZT1UUlVFLHdhcm5pbmc9RkFMU0UsbWVzc2FnZSA9IEZBTFNFLHJlc3VsdHM9J2hvbGQnLGNhY2hlLmxhenkgPSBGQUxTRSkKa25pdHI6Om9wdHNfa25pdCRzZXQoZXZhbC5hZnRlciA9ICdmaWcuY2FwJyxkZXY9YygncG5nJywncG9zdHNjcmlwdCcpKQoKCmxpYnJhcnkoY2lyY2xpemUpCmxpYnJhcnkoY2x1c3RlclByb2ZpbGVyKQpsaWJyYXJ5KGNvcnJwbG90KQpsaWJyYXJ5KENvbXBsZXhIZWF0bWFwKQpsaWJyYXJ5KGRwbHlyKQpsaWJyYXJ5KEVuc0RiLkhzYXBpZW5zLnY4NikgCmxpYnJhcnkoZ2RhdGEpCmxpYnJhcnkoZ2dwbG90MikKbGlicmFyeShnZ3B1YnIpCmxpYnJhcnkoZ2dyZXBlbCkKbGlicmFyeShncGxvdHMpCmxpYnJhcnkoZ3JpZCkKbGlicmFyeShncmlkRXh0cmEpCmxpYnJhcnkoZ3JpZEdyYXBoaWNzKQpsaWJyYXJ5KGthYmxlRXh0cmEpCmxpYnJhcnkoa25pdHIpCmxpYnJhcnkoTWF0cml4KQpsaWJyYXJ5KG9yZy5Icy5lZy5kYikKbGlicmFyeShwYXJhbGxlbCkKbGlicmFyeShyZXNoYXBlMikKbGlicmFyeShzY2FsZXMpCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KFNpbmdsZVIpCmxpYnJhcnkodGlkeXIpCmxpYnJhcnkodGlkeXZlcnNlKQpsaWJyYXJ5KHZpcmlkaXNMaXRlKQpsaWJyYXJ5KHhsc3gpCmxpYnJhcnkocmFuZG9tY29sb1IpCmxpYnJhcnkoa2FibGVFeHRyYSkKbGlicmFyeShnZGF0YSkKbGlicmFyeShrbml0cikKbGlicmFyeShubG1lKQpsaWJyYXJ5KHJnbCkKbGlicmFyeShncGxvdHMpCmxpYnJhcnkoV0dDTkEpCmxpYnJhcnkoeGxzeCkKbGlicmFyeShyYW5kb21jb2xvUikKbGlicmFyeShDb21wbGV4SGVhdG1hcCkKCmtuaXRfaG9va3Mkc2V0KHdlYmdsID0gaG9va193ZWJnbCkKCmhvbWUuZGlyPC0iL2hvbWUveWFueGl0aW5nL2RyaXZlcl9HcmFjZSIKI2hvbWUuZGlyPC0iL2hvbWUveHk0OCIKI3NvdXJjZShwYXN0ZShob21lLmRpciwiL1Jwcm9ncmFtL215X2Z1bmN0aW9ucy5SIixzZXA9IiIpKQpgYGAKIyBEYXRhIERvd25sb2FkaW5nCgojIyBzcmF0b29scwpXZSBkb3dubG9hZGVkIHRoZSBkYXRhIHVzaW5nIFNSQXRvb2xzIG9uIGdyYWNlIHVzaW5nIHRoZSBzY3JpcHRzIGdlbmVyYXRlZCBieSB0aGUgZm9sbG93aW5nIGNvZGVzLiBXZSBkb3dubG9hZGVkIHRoZSBzcmF0b29scyBmcm9tIGdpdGh1YiBhbmQgaW5zdGFsbGVkIGl0IG9uIGdyYWNlIHdpdGggc2V0dXAuIEFsbCBmaWxlcyBkb3dubG9hZGVkIHdlcmUgbm90IHppcHBlZCBzbyB3ZSBhbHNvIHppcHBlZCBlYWNoIGZpbGUgaW50byBmYXN0cS5neiBmaWxlLgoKYGBge3J9CmlkLmxpc3Q8LXJlYWRMaW5lcygiL1VzZXJzL3lhbnhpdGluZy9Eb3dubG9hZHMvU1JSX0FjY19MaXN0KDIpLnR4dCIpCnRlbXAxPC1wYXN0ZSgiL2hvbWUveHk0OC9zY3JhdGNoX3BhbG1lci9zcmF0b29sa2l0L3NyYXRvb2xraXQuMy4wLjAtdWJ1bnR1NjQvYmluL3ByZWZldGNoLjMuMC4wIC1YIDk5OTk5OTk5OTk5OTkgIixpZC5saXN0LCJcbiIsc2VwPSIiKQp0ZW1wMjwtcGFzdGUoIi9ob21lL3h5NDgvc2NyYXRjaF9wYWxtZXIvc3JhdG9vbGtpdC9zcmF0b29sa2l0LjMuMC4wLXVidW50dTY0L2Jpbi9mYXN0cS1kdW1wLjMuMC4wIC0tc3BsaXQtZmlsZXMgLi8iLGlkLmxpc3QsIi5zcmFcbiIsc2VwPSIiKQp0ZW1wMzwtcmVwKCJjZCAvaG9tZS94eTQ4L3NjcmF0Y2hfcGFsbWVyL1NBUkNfMTB4L3Rlc3Qvc3JhXG4iLGxlbmd0aCh0ZW1wMSkpCnRlbXA0PC1wYXN0ZSgiZ3ppcCAuLyIsaWQubGlzdCwiXyouZmFzdHFcbiIsc2VwPSIiKQp0ZW1wNTwtcmVwKCJzc2ggdHJhbnNmZXJcbiIsbGVuZ3RoKHRlbXAxKSkKY21kLm91dDwtY2JpbmQodGVtcDUsdGVtcDEsdGVtcDMsdGVtcDIsdGVtcDQpCnRlbXAuY21kPC1hcHBseShjbWQub3V0LDEscGFzdGUsY29sbGFwc2U9IiIsc2VwPSIiKQpjYXQodGVtcC5jbWQsZmlsZT0iLi9kb3dubG9hZF9jb21tYW5kcy50eHQiLGFwcGVuZD1GLHNlcD0iIikKCmBgYAoKIyMgY2hhbmdpbmcgbmFtZXMKVGhlIGNlbGwgcmFuZ2VyIHBpcGVsaW5lIHJlY29nbml6ZSBmYXN0cSBmaWxlIG5hbWVzIGFyZSBmb3JtYXRlZCBhcyBbU2FtcGxlIE5hbWVdX1MxX0wwMFtMYW5lIE51bWJlcl1fW1JlYWQgVHlwZV1fMDAxLmZhc3RxLmd6LiBUaGVyZSBhcmUgMyBmaWxlcyBwZXIgc2FtcGxlIHJlcHJlc2VudGluZyBJMSwgUjEgYW5kIFIyIGRvd25sb2FkZWQgYnkgc3JhdG9vbHMsIHdoaWNoIHdlcmUgbmFtZWQgYXMgKl8xLCAqXzIgYW5kICpfMy5mYXN0cSBmaWxlcy4gVG8gY29ycmVjdGx5IHJ1biBjZWxsIHJhbmdlciBvbiB0aGVzZSBmaWxlcywgd2UgbmVlZCB0byBjaGFuZ2UgdGhlIGZhc3RxLmd6IG5hbWVzLgoKYGBge3J9CiMgdGhpcyB3YXMgcnVuIG9uIGdyYWNlLgpzb3VyY2UoIn4vUnByb2dyYW0vbXlfZnVuY3Rpb25zLlIiKQpkYXRhLmRpcjwtIi9ob21lL3h5NDgvc2NyYXRjaF9wYWxtZXIvU0FSQ18xMHgvdGVzdC9zcmEiCmZpbGVuYW1lczwtbGlzdC5maWxlcyhkYXRhLmRpcikKdGVtcDwtc2FwcGx5KGZpbGVuYW1lcyxteS5lbGVtZW50LmV4dHJhY3Qsc3BsaXRjaGFyPSJcXC4iLGluZGV4PTMpCmZpbGVuYW1lczwtZmlsZW5hbWVzW3RlbXA9PSJneiJdCmZpbGVuYW1lczwtZmlsZW5hbWVzWyFpcy5uYShmaWxlbmFtZXMpXQoKZm9yKGkgaW4gMTpsZW5ndGgoZmlsZW5hbWVzKSl7CiAgCiAgaWYoc3Vic3RyKGZpbGVuYW1lc1tpXSwxLDQpPT0iU1JSOSIpewogIAlmcm9tLmZpbGVuYW1lPC1maWxlbmFtZXNbaV0KICAJdGVtcDwtbXkuZWxlbWVudC5leHRyYWN0KGZpbGVuYW1lc1tpXSxzcGxpdGNoYXI9IlxcLiIsaW5kZXg9MSkKICAJdGVtcDwtbXkuZWxlbWVudC5leHRyYWN0KHRlbXAsc3BsaXRjaGFyPSJfIixpbmRleD0yKQogIAlpZih0ZW1wPT0iMSIpewogIAkJdG8uZmlsZW5hbWU8LXBhc3RlMChteS5lbGVtZW50LmV4dHJhY3QoZmlsZW5hbWVzW2ldLHNwbGl0Y2hhcj0iXyIsaW5kZXg9MSksIl9TMV9MMDAxX0kxXzAwMS5mYXN0cS5neiIpCiAgCX0KICAJaWYodGVtcD09IjIiKXsKICAJCXRvLmZpbGVuYW1lPC1wYXN0ZTAobXkuZWxlbWVudC5leHRyYWN0KGZpbGVuYW1lc1tpXSxzcGxpdGNoYXI9Il8iLGluZGV4PTEpLCJfUzFfTDAwMV9SMV8wMDEuZmFzdHEuZ3oiKQogIAl9CiAgCWlmKHRlbXA9PSIzIil7CiAgCQl0by5maWxlbmFtZTwtcGFzdGUwKG15LmVsZW1lbnQuZXh0cmFjdChmaWxlbmFtZXNbaV0sc3BsaXRjaGFyPSJfIixpbmRleD0xKSwiX1MxX0wwMDFfUjJfMDAxLmZhc3RxLmd6IikKICAJfQogIAogIAlmaWxlLnJlbmFtZShmcm9tPWZpbGUucGF0aChkYXRhLmRpcixmcm9tLmZpbGVuYW1lKSx0bz1maWxlLnBhdGgoZGF0YS5kaXIsdG8uZmlsZW5hbWUpKQogIAljYXQoImZyb209IixmaWxlLnBhdGgoZGF0YS5kaXIsZnJvbS5maWxlbmFtZSksIlxuIiwidG89IixmaWxlLnBhdGgoZGF0YS5kaXIsdG8uZmlsZW5hbWUpLCJcbiIsc2VwPSIiKQogIH1lbHNlewogICAgCiAgCWZyb20uZmlsZW5hbWU8LWZpbGVuYW1lc1tpXQogIAl0ZW1wPC1teS5lbGVtZW50LmV4dHJhY3QoZmlsZW5hbWVzW2ldLHNwbGl0Y2hhcj0iXFwuIixpbmRleD0xKQogIAl0ZW1wPC1teS5lbGVtZW50LmV4dHJhY3QodGVtcCxzcGxpdGNoYXI9Il8iLGluZGV4PTIpCiAgCWlmKHRlbXA9PSIxIil7CiAgCQl0by5maWxlbmFtZTwtcGFzdGUwKG15LmVsZW1lbnQuZXh0cmFjdChmaWxlbmFtZXNbaV0sc3BsaXRjaGFyPSJfIixpbmRleD0xKSwiX1MxX0wwMDFfUjFfMDAxLmZhc3RxLmd6IikKICAJfQogIAlpZih0ZW1wPT0iMiIpewogIAkJdG8uZmlsZW5hbWU8LXBhc3RlMChteS5lbGVtZW50LmV4dHJhY3QoZmlsZW5hbWVzW2ldLHNwbGl0Y2hhcj0iXyIsaW5kZXg9MSksIl9TMV9MMDAxX1IyXzAwMS5mYXN0cS5neiIpCiAgCX0KICAJaWYodGVtcD09IjMiKXsKICAJCXRvLmZpbGVuYW1lPC1wYXN0ZTAobXkuZWxlbWVudC5leHRyYWN0KGZpbGVuYW1lc1tpXSxzcGxpdGNoYXI9Il8iLGluZGV4PTEpLCJfUzFfTDAwMV9JMV8wMDEuZmFzdHEuZ3oiKQogIAl9CiAgCiAgCWZpbGUucmVuYW1lKGZyb209ZmlsZS5wYXRoKGRhdGEuZGlyLGZyb20uZmlsZW5hbWUpLHRvPWZpbGUucGF0aChkYXRhLmRpcix0by5maWxlbmFtZSkpCiAgCWNhdCgiZnJvbT0iLGZpbGUucGF0aChkYXRhLmRpcixmcm9tLmZpbGVuYW1lKSwiXG4iLCJ0bz0iLGZpbGUucGF0aChkYXRhLmRpcix0by5maWxlbmFtZSksIlxuIixzZXA9IiIpICAgIAogICAgCiAgfQp9CmBgYAoKIyMgY2VsbCByYW5nZXIKCgpXZSByc3luYyB0aGUgZmFzdHEuZ3ogZmlsZXMgb250byBmYXJuYW0gdW5kZXIgfi9zY3JhdGNoNjAvU0FSQ18xMFgvZmFzdHEgYW5kIHJ1biBjZWxsIHJhbmdlciBvbiB0aGUgZmlsZXMgdXNpbmcgY29tbWFuZHMgZ2VuZXJhdGVkIGJ5IHRoZSBmb2xsb3dpbmcgY29kZXMuCgpgYGB7cn0KaWQubGlzdDwtcmVhZExpbmVzKCIvVXNlcnMveWFueGl0aW5nL0Rvd25sb2Fkcy9TUlJfQWNjX0xpc3QoMikudHh0IikKdGVtcDE8LXBhc3RlKCIjIS9iaW4vYmFzaFxuI1NCQVRDSCAtLXRpbWU9MjQ6MDA6MDAgIC0tbnRhc2tzPTEgLS1wYXJ0aXRpb249Z2VuZXJhbCAtLWNwdXMtcGVyLXRhc2s9OCAtLW1lbT04MEdCIC0tam9iLW5hbWU9IixpZC5saXN0LCIgLW8gL2hvbWUveHk0OC9zY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9zY3JpcHRzLyIsaWQubGlzdCwiLnNoLm8lSiAtZSAvaG9tZS94eTQ4L3NjcmF0Y2g2MC9TQVJDXzEwWC9jZWxscmFuZ2VyX3NjcmlwdHMvIixpZC5saXN0LCIuc2guZSVKXG4iLHNlcD0iIikKdGVtcDI8LXJlcCgibW9kdWxlIGxvYWQgY2VsbHJhbmdlci81LjAuMFxubW9kdWxlIGxvYWQgYmNsMmZhc3RxMi8yLTIwLTAtZm9zcy0yMDE4YlxuIixsZW5ndGgoaWQubGlzdCkpCnRlbXAzPC1wYXN0ZSgiY2QgL2hvbWUveHk0OC9zY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9yZXN1bHRzXG4iKQp0ZW1wNDwtcGFzdGUoImNlbGxyYW5nZXIgY291bnQgLS1pZD0iLGlkLmxpc3QsIiAtLWZhc3Rxcz0vaG9tZS94eTQ4L3NjcmF0Y2g2MC9TQVJDXzEwWC9mYXN0cSAtLXRyYW5zY3JpcHRvbWU9L2hvbWUveHk0OC9zY3JhdGNoNjAvU0FSQ18xMFgvcmVmZGF0YS1nZXgtR1JDaDM4LTIwMjAtQSAtLWxvY2FsY29yZXM9OCAtLWxvY2FsbWVtPTYwIC0tc2FtcGxlPSIsaWQubGlzdCwiXG4iLHNlcD0iIikKY21kLm91dDwtY2JpbmQodGVtcDEsdGVtcDIsdGVtcDMsdGVtcDQpCnRlbXAuY21kPC1hcHBseShjbWQub3V0LDEscGFzdGUsY29sbGFwc2U9IiIsc2VwPSIiKQpjYXQodGVtcC5jbWQsZmlsZT0iLi9jZWxscmFuZ2VyX3NjcmlwdHMudHh0IixhcHBlbmQ9RixzZXA9IiIpCmBgYAoKClRoZXJlIGFyZSBzYW1wbGVzIHdpdGggbXVsdGlwbGUgcnVucy4gV2UgcnVuIGNlbGxyYW5nZXIgYWdnciB0byBtZXJnZSB0aGUgbXVsdGlwbGUgcnVucyBpbnRvIG9uZSBzaW5nbGUgblVNSSB2ZWN0b3IuCgpgYGB7cn0Kc291cmNlKGZpbGUucGF0aChob21lLmRpciwiUnByb2dyYW0vbXlfZnVuY3Rpb25zLlIiKSkKbGlicmFyeSh4bHN4KQpob21lLmRpcjwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfRmFybmFtIgpydW50YWJsZS5maWxlcGF0aDwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoNjAvU0FSQ18xMFgvU3JhUnVuVGFibGUudHh0IikKaWQuZmlsZXBhdGg8LWZpbGUucGF0aChob21lLmRpciwic2NyYXRjaDYwL1NBUkNfMTBYL3NjUk5BX2lkcy54bHN4IikKCnJ1bi50YWJsZTwtcmVhZC50YWJsZShydW50YWJsZS5maWxlcGF0aCxzZXA9Ilx0IixoZWFkZXI9VCxjaGVjay5uYW1lcyA9IEYpCmlkLnRhYmxlPC1yZWFkLnhsc3goaWQuZmlsZXBhdGgsc2hlZXRJbmRleCA9IDEsY2hlY2submFtZXM9RikKCnRlbXAudGFibGU8LXJ1bi50YWJsZVtydW4udGFibGUkYEdFT19BY2Nlc3Npb24gKGV4cClgJWluJWFzLm1hdHJpeChpZC50YWJsZSlbLDFdLF0KdGVtcC5saXN0PC1zcGxpdChhcy5tYXRyaXgodGVtcC50YWJsZSlbLDFdLHRlbXAudGFibGUkYEdFT19BY2Nlc3Npb24gKGV4cClgKQoKIyBleHRyYWN0IHRoZSBzYW1wbGVzIHdpdGggbXVsdGlwbGUgcnVucwp0ZW1wLmxpc3Q8LXRlbXAubGlzdFt1bmxpc3QobGFwcGx5KHRlbXAubGlzdCxsZW5ndGgpKT4xXQoKIyBmaW5kIG91dCB0aGUgZmFzdHEgcGFydCB0aGVzZSBJRHMgYmVsb25nIHRvCnRlbXAuZmlsZW5hbWVzPC1saXN0LmZpbGVzKGZpbGUucGF0aChob21lLmRpciwic2NyYXRjaDYwL1NBUkNfMTBYIikpCnRlbXAuZmlsZW5hbWVzPC10ZW1wLmZpbGVuYW1lc1tncmVwKCIuZmlsZWxpc3QiLHRlbXAuZmlsZW5hbWVzKV0KdGVtcC5maWxlbmFtZXM8LXRlbXAuZmlsZW5hbWVzW2dyZXAoImZhc3RxIix0ZW1wLmZpbGVuYW1lcyldCgpmaWxlLmxpc3Q8LWxpc3QoKQpmb3IoaSBpbiAxOmxlbmd0aCh0ZW1wLmZpbGVuYW1lcykpewogIHRlbXA8LXJlYWRMaW5lcyhmaWxlLnBhdGgoaG9tZS5kaXIsInNjcmF0Y2g2MC9TQVJDXzEwWCIsdGVtcC5maWxlbmFtZXNbaV0pKQogIHRlbXA8LXVubmFtZShzYXBwbHkodGVtcCxteS5lbGVtZW50LmV4dHJhY3Qsc3BsaXRjaGFyPSIvIixpbmRleD0tMSkpCiAgdGVtcDwtdW5uYW1lKHNhcHBseSh0ZW1wLG15LmVsZW1lbnQuZXh0cmFjdCxzcGxpdGNoYXI9Il8iLGluZGV4PTEpKQogIHRlbXA8LXVuaXF1ZSh0ZW1wKQogIGZpbGUubGlzdFtbaV1dPC10ZW1wCn0KbmFtZXMoZmlsZS5saXN0KTwtdGVtcC5maWxlbmFtZXMKdGVtcDwtY2hhcmFjdGVyKCkKZm9yKGkgaW4gMTpsZW5ndGgoZmlsZS5saXN0KSl7CiAgdGVtcDwtYyh0ZW1wLHJlcChuYW1lcyhmaWxlLmxpc3QpW2ldLGxlbmd0aChmaWxlLmxpc3RbW2ldXSkpKQp9CgpmaWxlLmxpc3QubWF0cml4PC1jYmluZCh1bmxpc3QoZmlsZS5saXN0KSx0ZW1wKQoKdGVtcDwtbGlzdCgpCmZvcihpIGluIDE6bGVuZ3RoKHRlbXAubGlzdCkpewogIHRlbXBbW2ldXTwtdW5pcXVlKGZpbGUubGlzdC5tYXRyaXhbZmlsZS5saXN0Lm1hdHJpeFssMV0laW4ldGVtcC5saXN0W1tpXV0sMl0pCn0KCiMgdHJhbnNmZXIgdGhlIGNlbGxyYW5nZXIgcmVzdWx0cyBiYWNrIHRvIGZhcm5hbQojIGdlbmVyYXRlIHRoZSBhZ2dyZWdhdGlvbiBDU1YgZmlsZQpjZWxscmFuZ2VyLmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9yZXN1bHRzIikKb3V0cHV0LmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9hZ2dyX3NjcmlwdHMiKQoKZm9yKGkgaW4gMTpsZW5ndGgodGVtcC5saXN0KSl7CiAgb3V0cHV0LmZpbGVwYXRoPC1maWxlLnBhdGgob3V0cHV0LmRpcixwYXN0ZShuYW1lcyh0ZW1wLmxpc3QpW2ldLCJfYWdnci5jc3YiLHNlcD0iIikpCiAgIyBwaXBlbGluZSBiZWZvcmUgY2VsbHJhbmdlciA2LjAsIHVzZSBsaWJyYXJ5LiBPdGhlcndpc2UsIHVzZSBzYW1wbGVfaWQKICBjbWQub3V0PC1jKCJsaWJyYXJ5X2lkIiwibW9sZWN1bGVfaDUiKQogIGNtZC5vdXQ8LXJiaW5kKGNtZC5vdXQsY2JpbmQodGVtcC5saXN0W1tpXV0scGFzdGUoIi9ob21lL3h5NDgvc2NyYXRjaDYwL1NBUkNfMTBYL2NlbGxyYW5nZXJfcmVzdWx0cy8iLHRlbXAubGlzdFtbaV1dLCIvb3V0cy9tb2xlY3VsZV9pbmZvLmg1IixzZXA9IiIpKSkKICB3cml0ZS50YWJsZShjbWQub3V0LGZpbGU9b3V0cHV0LmZpbGVwYXRoLHJvdy5uYW1lcz1GLGNvbC5uYW1lcz1GLHNlcD0iLCIsYXBwZW5kPUYscXVvdGU9RikKfQoKIyBnZW5lcmF0ZSB0aGUgc2ggZmlsZSB0byBydW4gY2VsbHJhbmdlciBhZ2dyIG9uIHRoZSByZXBsaWNhdGVkIHJ1bnMgb2YgdGhlIHNhbWUgc2FtcGxlLgpzY3JpcHQuZGlyPC1maWxlLnBhdGgoaG9tZS5kaXIsInNjcmF0Y2g2MC9TQVJDXzEwWC9jZWxscmFuZ2VyX2FnZ3Jfc2NyaXB0cyIpCnJlc3VsdC5kaXI8LWZpbGUucGF0aChob21lLmRpciwic2NyYXRjaDYwL1NBUkNfMTBYL2NlbGxyYW5nZXJfYWdncl9yZXN1bHRzIikKCmZvcihpIGluIDE6bGVuZ3RoKHRlbXAubGlzdCkpewogIGNzdi5maWxlcGF0aDwtZmlsZS5wYXRoKHNjcmlwdC5kaXIscGFzdGUobmFtZXModGVtcC5saXN0KVtpXSwiX2FnZ3IuY3N2IixzZXA9IiIpKQogIHNjcmlwdC5maWxlcGF0aDwtZmlsZS5wYXRoKHNjcmlwdC5kaXIscGFzdGUobmFtZXModGVtcC5saXN0KVtpXSwiLnNoIixzZXA9IiIpKQogIGNtZC5vdXQ8LXBhc3RlKCIjIS9iaW4vYmFzaFxuI1NCQVRDSCAtLXRpbWU9MjQ6MDA6MDAgIC0tbnRhc2tzPTEgLS1wYXJ0aXRpb249Z2VuZXJhbCAtLWNwdXMtcGVyLXRhc2s9MSAtLW1lbT04MEdCIC0tam9iLW5hbWU9IixuYW1lcyh0ZW1wLmxpc3QpW2ldLCIgLW8gL2hvbWUveHk0OC9zY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9hZ2dyX3NjcmlwdHMvIixuYW1lcyh0ZW1wLmxpc3QpW2ldLCIuc2gubyVKIC1lIC9ob21lL3h5NDgvc2NyYXRjaDYwL1NBUkNfMTBYL2NlbGxyYW5nZXJfYWdncl9zY3JpcHRzLyIsbmFtZXModGVtcC5saXN0KVtpXSwiLnNoLmUlSlxuIixzZXA9IiIpCiAgY21kLm91dDwtcGFzdGUoY21kLm91dCwibW9kdWxlIGxvYWQgY2VsbHJhbmdlci81LjAuMFxubW9kdWxlIGxvYWQgYmNsMmZhc3RxMi8yLTIwLTAtZm9zcy0yMDE4YlxuIixzZXA9IiIpCiAgY21kLm91dDwtcGFzdGUoY21kLm91dCwiY2QgL2hvbWUveHk0OC9zY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9hZ2dyX3Jlc3VsdHNcbiIsc2VwPSIiKQogIGNtZC5vdXQ8LXBhc3RlKGNtZC5vdXQsImNlbGxyYW5nZXIgYWdnciAtLWlkPSIsbmFtZXModGVtcC5saXN0KVtpXSwiIC0tY3N2PS9ob21lL3h5NDgvc2NyYXRjaDYwL1NBUkNfMTBYL2NlbGxyYW5nZXJfYWdncl9zY3JpcHRzLyIsbmFtZXModGVtcC5saXN0KVtpXSwiX2FnZ3IuY3N2XG4iLHNlcD0iIikKICBjYXQoY21kLm91dCxmaWxlPXNjcmlwdC5maWxlcGF0aCxhcHBlbmQ9RikKfQoKYGBgCgpXZSBtb3ZlZCB0aGUgY2VsbHJhbmdlciBhZ2dyIHJlc3VsdHMgYmFjayB0byBjZWxscmFuZ2VyX3Jlc3VsdHMgdG9nZXRoZXIgd2l0aCB0aGUgc2FtcGxlcyB3aXRoIHVuaXF1ZSBydW4uCgojIyBVTUkgbWF0cml4IGV4dHJhY3Rpb24KCldlIGV4dHJhY3QgdGhlIG5VTUkgdmVjdG9yIGZvciB0aGUgc2FtcGxlcyBpbmNsdWRlZCBpbiB0aGUgb3JpZ2luYWwgcGFwZXIuCgoKYGBge3J9CmhvbWUuZGlyPC0iL2hvbWUveWFueGl0aW5nL2RyaXZlcl9HcmFjZSIKc291cmNlKGZpbGUucGF0aChob21lLmRpciwiUnByb2dyYW0vbXlfZnVuY3Rpb25zLlIiKSkKbGlicmFyeSh4bHN4KQpydW50YWJsZS5maWxlcGF0aDwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoX3BhbG1lci9TQVJDXzEweC9TcmFSdW5UYWJsZS50eHQiKQppZC5maWxlcGF0aDwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoX3BhbG1lci9TQVJDXzEweC9zY1JOQV9pZHMueGxzeCIpCgpydW4udGFibGU8LXJlYWQudGFibGUocnVudGFibGUuZmlsZXBhdGgsc2VwPSJcdCIsaGVhZGVyPVQsY2hlY2submFtZXMgPSBGKQppZC50YWJsZTwtcmVhZC54bHN4KGlkLmZpbGVwYXRoLHNoZWV0SW5kZXggPSAxLGNoZWNrLm5hbWVzPUYpCgpteS50YWJsZTwtcnVuLnRhYmxlW3J1bi50YWJsZSRgR0VPX0FjY2Vzc2lvbiAoZXhwKWAlaW4lYXMubWF0cml4KGlkLnRhYmxlKVssMV0sXQoKZmlsZW5hbWVzPC1saXN0LmZpbGVzKGZpbGUucGF0aChob21lLmRpciwic2NyYXRjaF9wYWxtZXIvU0FSQ18xMHgvY2VsbHJhbmdlcl9yZXN1bHRzIikpCgojIHVzZSBSZWFkMTB4IHRvIGxvYWQgaW4gdGhlIGRhdGEKZGlyLmxpc3Q8LXJlcCgiIixsZW5ndGgoZmlsZW5hbWVzKSkKc2FtcGxlLm5hbWVzPC1jaGFyYWN0ZXIoKQpmb3IoaSBpbiAxOmxlbmd0aChmaWxlbmFtZXMpKXsKICAKICBpZihzdWJzdHIoZmlsZW5hbWVzW2ldLDEsMSk9PSJHIil7CiAgICBkaXIubGlzdFtpXTwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoX3BhbG1lci9TQVJDXzEweC9jZWxscmFuZ2VyX3Jlc3VsdHMiLGZpbGVuYW1lc1tpXSwib3V0cyIsImNvdW50IiwiZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgiKQogIH1lbHNlewogICAgZGlyLmxpc3RbaV08LWZpbGUucGF0aChob21lLmRpciwic2NyYXRjaF9wYWxtZXIvU0FSQ18xMHgvY2VsbHJhbmdlcl9yZXN1bHRzIixmaWxlbmFtZXNbaV0sIm91dHMiLCJmaWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpCiAgfQogIAogIGlmKHN1YnN0cihmaWxlbmFtZXNbaV0sMSwxKT09IkciKXsKICAgIHNhbXBsZS5uYW1lczwtYyhzYW1wbGUubmFtZXMsZmlsZW5hbWVzW2ldKQogIH1lbHNlewogICAgc2FtcGxlLm5hbWVzPC1jKHNhbXBsZS5uYW1lcyxhcy5tYXRyaXgobXkudGFibGUpW215LnRhYmxlJFJ1bj09ZmlsZW5hbWVzW2ldLCJTYW1wbGUgTmFtZSJdKQogIH0KIAp9CgojIGR1cGxpY2F0ZWQgZ2VuZSBzeW1ib2xzIHdpbGwgYmUgY2hhbmdlZCB0byAuMSwgLjIsIGFuZCBzbyBvbi4KdGVtcDwtUmVhZDEwWChkYXRhLmRpcj1kaXIubGlzdCxnZW5lLmNvbHVtbj0yLGNlbGwuY29sdW1uPTEpCnRlbXAubnVtPC1hcy5udW1lcmljKHNhcHBseShjb2xuYW1lcyh0ZW1wKSxteS5lbGVtZW50LmV4dHJhY3Qsc3BsaXRjaGFyPSJfIixpbmRleD0xKSkKdGVtcDE8LXNhbXBsZS5uYW1lc1t0ZW1wLm51bV0KdGVtcDI8LXNhcHBseShjb2xuYW1lcyh0ZW1wKSxteS5lbGVtZW50LmV4dHJhY3Qsc3BsaXRjaGFyPSJfIixpbmRleD0tMSkKY29sbmFtZXModGVtcCk8LXBhc3RlKHRlbXAxLCJfIix0ZW1wMixzZXA9IiIpCm91dHB1dC5maWxlcGF0aDwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfR3JhY2Uvc2NyYXRjaF9wYWxtZXIvU0FSQ18xMHgvbWVyZ2VkX25VTUlfNDVfZ2VuZW5hbWVzX1JlYWQxMFgucmRzIgpzYXZlUkRTKHRlbXAsZmlsZT1vdXRwdXQuZmlsZXBhdGgscmVmaG9vaz1OVUxMKQoKCiNnY3FsMTwtQ3JlYXRlU2V1cmF0T2JqZWN0KGNvdW50cz10ZW1wKQoKbWVyZ2VkLmRhdGE8LW51bWVyaWMoKQpmb3IoaSBpbiAxOmxlbmd0aChmaWxlbmFtZXMpKXsKICBpZihzdWJzdHIoZmlsZW5hbWVzW2ldLDEsMSk9PSJHIil7CiAgICBtYXRyaXguZGlyPC1maWxlLnBhdGgoaG9tZS5kaXIsInNjcmF0Y2hfcGFsbWVyL1NBUkNfMTB4L2NlbGxyYW5nZXJfcmVzdWx0cyIsZmlsZW5hbWVzW2ldLCJvdXRzIiwiY291bnQiLCJmaWx0ZXJlZF9mZWF0dXJlX2JjX21hdHJpeCIpCiAgfWVsc2V7CiAgICBtYXRyaXguZGlyPC1maWxlLnBhdGgoaG9tZS5kaXIsInNjcmF0Y2hfcGFsbWVyL1NBUkNfMTB4L2NlbGxyYW5nZXJfcmVzdWx0cyIsZmlsZW5hbWVzW2ldLCJvdXRzIiwiZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgiKQogIH0KICAKICBiYXJjb2RlLnBhdGggPC0gZmlsZS5wYXRoKG1hdHJpeC5kaXIsICJiYXJjb2Rlcy50c3YuZ3oiKQogIGZlYXR1cmVzLnBhdGggPC0gZmlsZS5wYXRoKG1hdHJpeC5kaXIsICJmZWF0dXJlcy50c3YuZ3oiKQogIG1hdHJpeC5wYXRoIDwtIGZpbGUucGF0aChtYXRyaXguZGlyLCAibWF0cml4Lm10eC5neiIpCiAgbWF0IDwtIHJlYWRNTShmaWxlID0gbWF0cml4LnBhdGgpCiAgZmVhdHVyZS5uYW1lcyA9IHJlYWQuZGVsaW0oZmVhdHVyZXMucGF0aCwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBoZWFkZXIgPSBGQUxTRSwKICAgICAgICAgICAgICAgICAgICAgICAgICAgICBzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCiAgYmFyY29kZS5uYW1lcyA9IHJlYWQuZGVsaW0oYmFyY29kZS5wYXRoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICBpZihzdWJzdHIoZmlsZW5hbWVzW2ldLDEsMSk9PSJHIil7CiAgICBzYW1wbGUubmFtZTwtZmlsZW5hbWVzW2ldCiAgfWVsc2V7CiAgICBzYW1wbGUubmFtZTwtYXMubWF0cml4KG15LnRhYmxlKVtteS50YWJsZSRSdW49PWZpbGVuYW1lc1tpXSwiU2FtcGxlIE5hbWUiXQogIH0KICAKICBjb2xuYW1lcyhtYXQpID0gcGFzdGUwKHNhbXBsZS5uYW1lLCJfIixiYXJjb2RlLm5hbWVzJFYxKQogIHJvd25hbWVzKG1hdCkgPSBmZWF0dXJlLm5hbWVzJFYyCiAgCiAgaWYoaT09MSl7CiAgICBtZXJnZWQuZGF0YTwtbWF0CiAgICBnZW5lLm5hbWVzPC1yb3duYW1lcyhtYXQpCiAgfWVsc2V7CiAgICBtZXJnZWQuZGF0YTwtY2JpbmQobWVyZ2VkLmRhdGEsbWF0W2dlbmUubmFtZXMsXSkKICB9Cn0KCm91dHB1dC5maWxlcGF0aDwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfR3JhY2Uvc2NyYXRjaF9wYWxtZXIvU0FSQ18xMHgvbWVyZ2VkX25VTUlfNDVfZ2VuZW5hbWVzLnJkcyIKc2F2ZVJEUyhtZXJnZWQuZGF0YSxmaWxlPW91dHB1dC5maWxlcGF0aCxyZWZob29rPU5VTEwpCgpgYGAKCldlIHRyYW5zZmVycmVkIHRoZSBjZWxscmFuZ2VyIHJlc3VsdHMgYmFjayB0byBmYXJuYW0gdG8gZXh0cmFjdCB0aGUgblVNSSByZXN1bHRzLiBXZSBhbHNvIGFkZCB0aGUgc2FtcGxlcyB3aXRoIG11bHRpcGxlIHJ1bnMgaW50byB0aGUgbWVyZ2VkIG1hdHJpeC4KCmBgYHtyIGV2YWw9RkFMU0V9CiMgc2FtcGxlcyB3aXRoIG11bHRpcGxlIHJ1bnMgaGF2ZSBuYW1lcyBvZiB0aGUgR0VPIGFjY2Vzc2lvbiBudW1iZXIuCnNvdXJjZSgiL2hvbWUveWFueGl0aW5nL2RyaXZlcl9GYXJuYW0vUnByb2dyYW0vbXlfZnVuY3Rpb25zLlIiKQpsaWJyYXJ5KE1hdHJpeCkKY2VsbHJhbmdlci5kaXI8LSIvaG9tZS95YW54aXRpbmcvZHJpdmVyX0Zhcm5hbS9zY3JhdGNoNjAvU0FSQ18xMFgvY2VsbHJhbmdlcl9yZXN1bHRzIgpydW50YWJsZS5maWxlcGF0aDwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfRmFybmFtL3NjcmF0Y2g2MC9TQVJDXzEwWC9TcmFSdW5UYWJsZS50eHQiCgojIGxvYWQgaW4gdGhlIHJ1biB0YWJsZQpydW4udGFibGU8LXJlYWQudGFibGUocnVudGFibGUuZmlsZXBhdGgsc2VwPSJcdCIsaGVhZGVyPVQsY2hlY2submFtZXM9RixzdHJpbmdzQXNGYWN0b3JzID0gRkFMU0UpCgojIGxvYWQgaW4gdGhlIHJ1biBuYW1lcyBhbmQgdGhlIEdTTSBJRHMgdW5kZXIgdGhlIGNlbGwgcmFuZ2VyIHJlc3VsdHMgZm9sZGVyCnRlbXAuZmlsZW5hbWVzPC1saXN0LmZpbGVzKGNlbGxyYW5nZXIuZGlyKQoKc2FtcGxlLnJlcDwtdGVtcC5maWxlbmFtZXNbc3Vic3RyKHRlbXAuZmlsZW5hbWVzLDEsMyk9PSJHU00iXQpzYW1wbGUudW5pPC10ZW1wLmZpbGVuYW1lc1tzdWJzdHIodGVtcC5maWxlbmFtZXMsMSwzKSE9IkdTTSJdCm15LnJ1bi50YWJsZTwtcnVuLnRhYmxlW2FzLm1hdHJpeChydW4udGFibGUpWywiU2FtcGxlIE5hbWUiXSVpbiVzYW1wbGUucmVwIHwgYXMubWF0cml4KHJ1bi50YWJsZSlbLCJSdW4iXSVpbiVzYW1wbGUudW5pLF0KIyBleHRyYWN0IHRoZSBuVU1JIG1hdHJpeApkaXJuYW1lczwtbGlzdC5maWxlcyhjZWxscmFuZ2VyLmRpcikKZGlybmFtZXM8LWRpcm5hbWVzW3N1YnN0cihkaXJuYW1lcywxLDQpPT0iU1JSMSJdCgpmb3IoaSBpbiAxOmxlbmd0aChkaXJuYW1lcykpewogIHJ1bi5uYW1lPC1kaXJuYW1lc1tpXQogIHNhbXBsZS5uYW1lPC1ydW4udGFibGVbcnVuLnRhYmxlWywiUnVuIl09PXJ1bi5uYW1lLCJTYW1wbGUgTmFtZSJdCiAgbWF0cml4X2RpcjwtZmlsZS5wYXRoKGNlbGxyYW5nZXIuZGlyLGRpcm5hbWVzW2ldLCJvdXRzIiwiZmlsdGVyZWRfZmVhdHVyZV9iY19tYXRyaXgiKQogIGJhcmNvZGUucGF0aCA8LSBmaWxlLnBhdGgobWF0cml4X2RpciwgImJhcmNvZGVzLnRzdi5neiIpCiAgZmVhdHVyZXMucGF0aCA8LSBmaWxlLnBhdGgobWF0cml4X2RpciwgImZlYXR1cmVzLnRzdi5neiIpCiAgbWF0cml4LnBhdGggPC0gZmlsZS5wYXRoKG1hdHJpeF9kaXIsICJtYXRyaXgubXR4Lmd6IikKICBtYXQgPC0gcmVhZE1NKGZpbGUgPSBtYXRyaXgucGF0aCkKICBmZWF0dXJlLm5hbWVzID0gcmVhZC5kZWxpbShmZWF0dXJlcy5wYXRoLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIGhlYWRlciA9IEZBTFNFLAogICAgICAgICAgICAgICAgICAgICAgICAgICAgIHN0cmluZ3NBc0ZhY3RvcnMgPSBGQUxTRSkKICBiYXJjb2RlLm5hbWVzID0gcmVhZC5kZWxpbShiYXJjb2RlLnBhdGgsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgaGVhZGVyID0gRkFMU0UsCiAgICAgICAgICAgICAgICAgICAgICAgICAgICAgc3RyaW5nc0FzRmFjdG9ycyA9IEZBTFNFKQogIGNvbG5hbWVzKG1hdCkgPSBwYXN0ZTAoc2FtcGxlLm5hbWUsIl8iLGJhcmNvZGUubmFtZXMkVjEpCiAgcm93bmFtZXMobWF0KSA9IGZlYXR1cmUubmFtZXMkVjEKICAKICBvdXRwdXQuZmlsZXBhdGg8LWZpbGUucGF0aCgiL2hvbWUveWFueGl0aW5nL2RyaXZlcl9GYXJuYW0vc2NyYXRjaDYwL1NBUkNfMTBYL2NlbGxyYW5nZXJfcmVzdWx0cyIscGFzdGUwKHNhbXBsZS5uYW1lLCJfIixydW4ubmFtZSwiX25VTUkucmRzIikpCiAgc2F2ZVJEUyhtYXQsZmlsZT1vdXRwdXQuZmlsZXBhdGgscmVmaG9vaz1OVUxMKQp9CgpgYGAKCgpTaW5jZSB3ZSBoYXZlIGxpbWl0ZWQgc3RvcmFnZSBvbiB0aGUgaHBjLCB3ZSBiYWNrIHVwIHRoZSBmYXN0cS5neiBmaWxlcyBhbmQgdGhlIGNlbGwgcmFuZ2VyIG91dHB1dCBmb2xkZXJzLiBXZSBleHRyYWN0ZWQgdGhlIG5VTUkgbWF0cml4IG9mIGVhY2ggc2FtcGxlIHNlcGFyYXRlbHkgYW5kIGRlbGV0ZWQgYWxsIG90aGVyIGZpbGVzIHRvIG1ha2Ugcm9vbS4gQWxsIGZhc3RxLmd6IGZpbGVzIGFuZCB0aGVpciBjb3JyZXNwb25kaW5nIGNlbGwgcmFuZ2VyIG91dHB1dCBmb2xkZXJzIGFyZSBiYWNrZWQgdXAgb24gZ29vZ2xlIGRyaXZlIHVuZGVyIC9Wb2x1bWVzL0dvb2dsZURyaXZlL015IERyaXZlL0dyYWNlQmFja3VwL0dSQURTL1NBUkNfUEJNQy9TQVJDXzEwWC4KCiMjIFNldXJhdCBBbmFseXNpcwoKV2UgbG9hZCB0aGUgZGF0YSBpbnRvIFNldXJhdCBmb3IgZG93bnN0cmVhbSBhbmFseXNpcywgZXNwZWNpYWxseSBmb3IgdGhlIGludGVncmF0ZWQgYW5hbHlzaXMuCgpGaXJzdCwgd2UgY3JlYXRlIHRoZSBTZXVyYXQgb2JqZWN0IGFuZCBwcmVsaW1pbmFyeSBmaWx0ZXJpbmcgb24gZ2VuZXMgYW5kCmBgYHtyfQojIGR1ZSB0byB0aGUgdW5hdmFpbGFiaWxpdHkgb2YgdGhlIGNsdXN0ZXIsIHdlIHJhbiB0aGVzZSBjb2RlcyBvbiB0YWM0IHNlcnZlciB3aXRoIHRoZSBzYW1lIHZlcnNpb25zIG9mIFIgYW5kIHBhY2thZ2VzCiMgc3J1biAtLXB0eSAtLXgxMSAtcCBwaV9rYW1pbnNraSAtdCAxMjowMDowMCAtLW50YXNrcz0xIC0tbm9kZXM9MSAtLWNwdXMtcGVyLXRhc2s9MSAtLW1lbT02MDE1MiBiYXNoCiMgbW9kdWxlIHJlc3RvcmUgc2V1cmF0CiNob21lLmRpcjwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfRmFybmFtIgpob21lLmRpcjwtIi9ob21lL3lhbnhpdGluZy9Eb2N1bWVudHMvUmVzZWFyY2gvR1JBRFNfU0FSQ19QQk1DIgpzb3VyY2UoIi9ob21lL3lhbnhpdGluZy9ScHJvZ3JhbS9teV9mdW5jdGlvbnMuUiIpCgojb3V0cHV0LmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoNjAvU0FSQ18xMFgvU2V1cmF0IikKb3V0cHV0LmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY1JOQS1zZXEvU2V1cmF0IikKCmlmKGZpbGUuZXhpc3RzKG91dHB1dC5kaXIpPT1GKXsKZGlyLmNyZWF0ZShvdXRwdXQuZGlyKQp9CgojIExvYWQgdGhlIG1lcmdlZCBkYXRhCm1lcmdlZC5kYXRhPC1yZWFkUkRTKGZpbGUucGF0aChob21lLmRpciwiRGF0YS9tZXJnZWRfblVNSV80NV9nZW5lbmFtZXNfUmVhZDEwWC5yZHMiKSxyZWZob29rPU5VTEwpCgojZ2VvZmYuZGF0YSA8LSBSZWFkMTBYKGRhdGEuZGlyID0gZmlsZS5wYXRoKGRhdGEuZGlyLCJvdXRzIiwiZmlsdGVyZWRfZ2VuZV9iY19tYXRyaWNlc19tZXgiLCJHUkNoMzgiKSkKI2xvYWQoZmlsZS5wYXRoKGhvbWUuZGlyLCJzY3JhdGNoX2thbWluc2tpL3B1YmxpYy9CYWNrdXAvSm9uYXMvUl9vYmplY3RzLzEweF9DaHVwcEFzdGhtYS5tdHguaHlicmlkX2dlbmVfc3ltYm9sc193b19iYWNrZ3JvdW5kXzAzXzExMTkuUm9iaiIpKQpnY3FsMSA8LSBDcmVhdGVTZXVyYXRPYmplY3QoY291bnRzID0gbWVyZ2VkLmRhdGEsIHByb2plY3QgPSAiU0FSQ18xMFgiLG1pbi5jZWxscyA9IDMsICBtaW4uZmVhdHVyZXMgPSAyMDApCmdjcWwxW1sicGVyY2VudC5tdCJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChnY3FsMSwgcGF0dGVybiA9ICJeTVQtIikKCmNhdCgiXG4iKQpjYXQoIk9yaWdpbmFsbHksIHRoZXJlIGFyZSAiLG5yb3cobWVyZ2VkLmRhdGEpLCIgZ2VuZXMgYW5kICIsbmNvbChtZXJnZWQuZGF0YSksIiBjZWxscyBpbiB0aGUgZGF0YS5cbiIsc2VwPSIiKQpjYXQoIkFmdGVyIHRoZSBmaXJzdCBzdGVwIGZpbHRlcmluZywgdGhlcmUgYXJlICIsbnJvdyhnY3FsMUBhc3NheXMkUk5BQGRhdGEpLCIgZ2VuZXMgYW5kICIsbmNvbChnY3FsMUBhc3NheXMkUk5BQGRhdGEpLCIgY2VsbHMgaW4gdGhlIGZpbHRlcmVkIGRhdGEuXG4iLHNlcD0iIikKCnJtKG1lcmdlZC5kYXRhKQojZ2ModmVyYm9zZT1GKQppbnZpc2libGUoZ2MoKSkKCiMgbG9hZCBpbiB0aGUgcGhlbm90eXBlIGRhdGEgb2YgYWxsIHRoZSBzYW1wbGVzCmlkLmZpbGVwYXRoPC1maWxlLnBhdGgoaG9tZS5kaXIsIkRhdGEvc2NSTkFfaWRzLnhsc3giKQpydW50YWJsZS5maWxlcGF0aDwtZmlsZS5wYXRoKGhvbWUuZGlyLCJEYXRhL1NyYVJ1blRhYmxlLnR4dCIpCgpydW4udGFibGU8LXJlYWQudGFibGUocnVudGFibGUuZmlsZXBhdGgsc2VwPSJcdCIsaGVhZGVyPVQsY2hlY2submFtZXMgPSBGKQppZC50YWJsZTwtcmVhZC54bHN4KGlkLmZpbGVwYXRoLHNoZWV0SW5kZXggPSAxLGNoZWNrLm5hbWVzPUYpCgojIGdldCB0aGUgc2FtcGxlIG5hbWVzIGZvciBhbGwgdGhlIGNlbGxzCm15LmlkZW50PC11bm5hbWUoc2FwcGx5KGNvbG5hbWVzKGdjcWwxQGFzc2F5cyRSTkFAY291bnRzKSxteS5lbGVtZW50LmV4dHJhY3Qsc3BsaXRjaGFyPSJfIixpbmRleD0xKSkKbmFtZXMobXkuaWRlbnQpPC1jb2xuYW1lcyhnY3FsMUBhc3NheXMkUk5BQGNvdW50cykKCnRlbXA8LXNwbGl0KGFzLm1hdHJpeChydW4udGFibGUpWywic3R1ZHlfY2xhc3NpZmljYXRpb24iXSxhcy5tYXRyaXgocnVuLnRhYmxlKVssIlNhbXBsZSBOYW1lIl0pCnRlbXA8LWxhcHBseSh0ZW1wLHVuaXF1ZSkKbXkuZGlzZWFzZTwtdW5saXN0KHRlbXBbbXkuaWRlbnRdKQoKbXkuc2FtcGxlbmFtZXM8LW15LmlkZW50CmRpc2Vhc2Uuc2FtcGxlbmFtZXM8LXBhc3RlKG15LmRpc2Vhc2UsIl8iLG15LnNhbXBsZW5hbWVzLHNlcD0iIikKCiMgc2NSTkEtc2VxIFFDIG1ldHJpYy4KI21pdG8uZ2VuZXMxIDwtIGdyZXAocGF0dGVybiA9ICJeTVQtIiwgeCA9IHJvd25hbWVzKGdjcWwxQGFzc2F5cyRSTkFAY291bnRzKSwgdmFsdWUgPSBUUlVFKQojcGVyY2VudC5taXRvMSA8LSBNYXRyaXg6OmNvbFN1bXMoZ2NxbDFAYXNzYXlzJFJOQUBjb3VudHNbbWl0by5nZW5lczEsIF0pL01hdHJpeDo6Y29sU3VtcyhnY3FsMUBhc3NheXMkUk5BQGNvdW50cykKCiMgQWRkTWV0YURhdGEgYWRkcyBjb2x1bW5zIHRvIG9iamVjdEBtZXRhLmRhdGEsIGFuZCBpcyBhIGdyZWF0IHBsYWNlIHRvCiMgc3Rhc2ggUUMgc3RhdHMKZ2NxbDEgPC0gQWRkTWV0YURhdGEob2JqZWN0ID0gZ2NxbDEsIG1ldGFkYXRhID0gbXkuZGlzZWFzZSwgY29sLm5hbWUgPSAiZGlzZWFzZSIpCmdjcWwxIDwtIEFkZE1ldGFEYXRhKG9iamVjdCA9IGdjcWwxLCBtZXRhZGF0YSA9IG15LnNhbXBsZW5hbWVzLCBjb2wubmFtZSA9ICJzYW1wbGUubmFtZXMiKQpnY3FsMSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBnY3FsMSwgbWV0YWRhdGEgPSBteS5pZGVudCwgY29sLm5hbWUgPSAic2FtcGxlIikKZ2NxbDEgPC0gQWRkTWV0YURhdGEob2JqZWN0ID0gZ2NxbDEsIG1ldGFkYXRhID0gZGlzZWFzZS5zYW1wbGVuYW1lcywgY29sLm5hbWUgPSAiZGlzZWFzZS5zYW1wbGVuYW1lcyIpCnRlbXA8LXJlcCgicGJtYyIsbnJvdyhnY3FsMUBtZXRhLmRhdGEpKQpnY3FsMSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBnY3FsMSwgbWV0YWRhdGEgPSB0ZW1wLCBjb2wubmFtZSA9ICJ0aXNzdWUiKQoKIyBmaWx0ZXIgdGhlIGRhdGEgdG8gb25seSBrZWVwIGdlbmVzIHByZXNlbnQgKD4wIGluID49MSUgb2YgYWxsIHRoZSBjZWxscykgaW4gPj0zIHN1YmplY3RzCmdlbmUucHJlczwtbnVtZXJpYygpCnNhbXBsZS5uYW1lcy51bmlxdWU8LXVuaXF1ZShnY3FsMUBtZXRhLmRhdGEkc2FtcGxlLm5hbWVzKQoKZm9yKGkgaW4gMTpsZW5ndGgoc2FtcGxlLm5hbWVzLnVuaXF1ZSkpewogIHRlbXAubWF0cml4PC1nY3FsMUBhc3NheXMkUk5BQGNvdW50c1ssZ2NxbDFAbWV0YS5kYXRhJHNhbXBsZS5uYW1lcz09c2FtcGxlLm5hbWVzLnVuaXF1ZVtpXV0KICB0ZW1wLnZlY3Q8LWFwcGx5KHRlbXAubWF0cml4PjAsMSxzdW0pCiAgZ2VuZS5wcmVzPC1jYmluZChnZW5lLnByZXMsdGVtcC52ZWN0Pj0obmNvbCh0ZW1wLm1hdHJpeCkqMC4wMSkpCn0KCmdlbmUubmFtZXM8LXJvd25hbWVzKGdjcWwxQGFzc2F5cyRSTkFAY291bnRzKVthcHBseShnZW5lLnByZXMsMSxzdW0pPj0zXQpnY3FsMS5vcmlnPC1nY3FsMQpnY3FsMTwtc3Vic2V0KGdjcWwxLm9yaWcsZmVhdHVyZXM9Z2VuZS5uYW1lcykKCmNhdCgiQWZ0ZXIgdGhlIGZpcnN0IGFuZCBzZWNvbmQgc3RlcHMgb2YgZmlsdGVyaW5nLCB0aGVyZSBhcmUgIixucm93KGdjcWwxQGFzc2F5cyRSTkFAY291bnRzKSwiIGdlbmVzIGFuZCAiLG5jb2woZ2NxbDFAYXNzYXlzJFJOQUBjb3VudHMpLCIgY2VsbHMgaW4gdGhlIGRhdGEuXG4iLHNlcD0iIikKYGBgCgoKU2Vjb25kLCB3ZSBoYWQgYSB0aGlyZCBmaWx0ZXJpbmcgc3RlcCBvbiBjZWxscyBiYXNlZCBvbiB0aGUgbkdlbmUgYW5kIHBlcmNlbnQubWl0by4gSGVyZSBhcmUgdGhlIHBsb3RzIHRvIGhlbHAgdXMgZGVjaWRlIHRoZSB0aHJlc2hvbGQgb2YgdGhlIGZpbHRlcmluZy4KYGBge3IgZmlnLndpZHRoPTIwLGZpZy5oZWlnaHQ9MTAsY2FjaGU9RkFMU0V9CgojZzE8LVZsblBsb3Qob2JqZWN0ID0gZ2NxbDEsIGZlYXR1cmVzID0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAicGVyY2VudC5tdCIpLCBuY29sID0gMyxncm91cC5ieT0ic2FtcGxlbmFtZXMiLGNvbWJpbmU9VCkKCmcyPC1WbG5QbG90KG9iamVjdCA9IGdjcWwxLCBmZWF0dXJlcz0gYygibkZlYXR1cmVfUk5BIiwgIm5Db3VudF9STkEiLCAicGVyY2VudC5tdCIpLCBuY29sID0gMyxncm91cC5ieSA9ICJkaXNlYXNlLnNhbXBsZW5hbWVzIixjb21iaW5lPVQpCmdyaWQuYXJyYW5nZShnMixuY29sPTEpCmBgYAoKCmBgYHtyIGZpZy53aWR0aD0xNCxmaWcuaGVpZ2h0PTQsZWNobz1GQUxTRSxjYWNoZT1GQUxTRSxyZXN1bHRzPSdhc2lzJ30KIyBzaG93IHRoZSAyRCBzY2F0dGVycGxvdApnMTwtRmVhdHVyZVNjYXR0ZXIoZ2NxbDEsZmVhdHVyZTE9Im5Db3VudF9STkEiLGZlYXR1cmUyPSJwZXJjZW50Lm10Iixncm91cC5ieSA9ICJ0aXNzdWUiLHB0LnNpemU9MC41KQpnMTwtZzErZ2VvbV9hYmxpbmUoaW50ZXJjZXB0PTEwLHNsb3BlPTAsY29sPSJyZWQiKSAKCmcyPC1GZWF0dXJlU2NhdHRlcihnY3FsMSxmZWF0dXJlMT0ibkNvdW50X1JOQSIsZmVhdHVyZTI9Im5GZWF0dXJlX1JOQSIsZ3JvdXAuYnkgPSAidGlzc3VlIixwdC5zaXplPTAuNSkKZzI8LWcyK2dlb21fYWJsaW5lKGludGVyY2VwdD0zMDAsc2xvcGU9MCxjb2w9InJlZCIpCmcyPC1nMitnZW9tX2FibGluZShpbnRlcmNlcHQ9NjAwMCxzbG9wZT0wLGNvbD0icmVkIikKCmczPC1GZWF0dXJlU2NhdHRlcihnY3FsMSxmZWF0dXJlMT0ibkZlYXR1cmVfUk5BIixmZWF0dXJlMj0icGVyY2VudC5tdCIsZ3JvdXAuYnkgPSAidGlzc3VlIixwdC5zaXplPTAuNSkKZzM8LWczK2dlb21fYWJsaW5lKGludGVyY2VwdD0xMCxzbG9wZT0wLGNvbD0icmVkIikKZzM8LWczK2dlb21fdmxpbmUoeGludGVyY2VwdD0zMzAsY29sPSJyZWQiKQpnMzwtZzMrZ2VvbV92bGluZSh4aW50ZXJjZXB0PTYwMDAsY29sPSJyZWQiKQpnPC1ncmlkLmFycmFuZ2UoZzEsZzIsZzMsbmNvbD0zKQpnZ3NhdmUoZmlsZW5hbWU9ZmlsZS5wYXRoKG91dHB1dC5kaXIsIjFfZGF0YXByZXByb2Nlc3NpbmdfRmVhdHVyZVNjYXR0ZXIucGRmIikscGxvdD1nLGRldmljZT0icGRmIix1bml0cyA9ICJpbiIsd2lkdGg9MTQsaGVpZ2h0PTQpCmBgYAoKYGBge3J9CiMgb25seSBrZWVwIGNlbGxzIHdpdGggbkZlYXR1cmVfUk5BPj0zMzAgb3IgPD02MDAwCiNnY3FsMTwtc3Vic2V0KGdjcWwxLHN1YnNldD0gbkZlYXR1cmVfUk5BID49IDMwMCAmIG5GZWF0dXJlX1JOQTw9IDYwMDAgJiBwZXJjZW50Lm10PD0gNTApCmdjcWwxPC1zdWJzZXQoZ2NxbDEsc3Vic2V0PSBuRmVhdHVyZV9STkEgPj0gMzAwICYgcGVyY2VudC5tdDw9IDUwKQpjYXQoIkFmdGVyIHRoZSB0aGlyZCBmaWx0ZXJpbmcgc3RlcCwgd2UgaGF2ZSAiLG5yb3coZ2NxbDFAYXNzYXlzJFJOQUBjb3VudHMpLCIgZ2VuZXMgYW5kICIsbmNvbChnY3FsMUBhc3NheXMkUk5BQGNvdW50cyksIiBjZWxscy5cbiIsc2VwPSIiKQpgYGAKClRoaXJkLCB3ZSBub3JtYWxpemUgdGhlIHVuaW1wdXRlZCBhbmQgc2F2ZSB0aGUgU0FWRVIgaW1wdXRlZCBkYXRhIGFzIHRoZSBub3JtYWxpemVkIGRhdGEuIFRoZXNlIGRhdGEgYXJlIHNhdmVkIGZvciBkb3duc3RyZWFtIGZ1cnRoZXIgcHJvY2Vzc2luZy4KCmBgYHtyfQojIG5vcm1hbGl6ZSB0aGUgZGF0YSBhbmQgc2F2ZSB0aGUgcmVzdWx0cwpnY3FsMSA8LSBOb3JtYWxpemVEYXRhKGdjcWwxLCBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJMb2dOb3JtYWxpemUiLCAgc2NhbGUuZmFjdG9yID0gMTAwMDApCnNhdmVSRFMoZ2NxbDEsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0LmRpciwiMV9ub3JtYWxpemVkX2RhdGFfdW5pbXB1dGVkXzEucmRzIikpCgojZ2NxbDEuc2F2ZXI8LU5vcm1hbGl6ZURhdGEob2JqZWN0ID0gZ2NxbDEuc2F2ZXIsIG5vcm1hbGl6YXRpb24ubWV0aG9kID0gIkxvZ05vcm1hbGl6ZSIsICBzY2FsZS5mYWN0b3IgPSAxMDAwMCkKI3NhdmVSRFMoZ2NxbDEuc2F2ZXIsIGZpbGUgPSBmaWxlLnBhdGgob3V0cHV0LmRpciwicHJlcHJvY2Vzc2VkX2RhdGFfc2F2ZXJpbXB1dGVkXzEucmRzIikpCgpjYXQoInRoZSBwcmVwcm9jZXNzZWQgZGF0YSBmb3IgdGhlIHVuaW1wdXRlZCBkYXRhIHdhcyBzYXZlZCBhcyAiLGZpbGUucGF0aChvdXRwdXQuZGlyLCIxX25vcm1hbGl6ZWRfZGF0YV91bmltcHV0ZWRfMS5yZHMiKSwiXG4iLHNlcD0iIikKCmNhdCgiQWZ0ZXIgdGhlIG5vcm1hbGl6YXRpb24sIHRoZXJlIGFyZSAiLG5yb3coZ2NxbDFAYXNzYXlzJFJOQUBkYXRhKSwiIGdlbmVzIGFuZCAiLG5jb2woZ2NxbDFAYXNzYXlzJFJOQUBkYXRhKSwiIGNlbGxzXG4iLHNlcD0iIikKCiNjYXQoInRoZSBwcmVwcm9jZXNzZWQgZGF0YSBmb3IgdGhlIFNBVkVSIGltcHV0ZWQgZGF0YSB3YXMgc2F2ZWQgYXMgIixmaWxlLnBhdGgob3V0cHV0LmRpciwicHJlcHJvY2Vzc2VkX2RhdGFfc2F2ZXJpbXB1dGVkXzEucmRzIiksIlxuIixzZXA9IiIpCmBgYAoKIyMjIE9yaWdpbmFsIENlbGwgQ2x1c3RlcmluZwoKRmlyc3QsIHdlIGZpbmQgdmFyaWFibGUgZ2VuZXMuCmBgYHtyfQpmZWF0dXJlcy5uPC0yMDAwCmhvbWUuZGlyPC0iL2hvbWUveWFueGl0aW5nL0RvY3VtZW50cy9SZXNlYXJjaC9HUkFEU19TQVJDX1BCTUMiCmBgYAoKV2UgbG9hZCBpbiB0aGUgbm9ybWFsaXplZCB1bmltcHV0ZWQgZGF0YSBhbmQgZmluZCB0aGUgdG9wIHZhcmlhYmxlIGdlbmVzIGZvciBjZWxsIGNsdXN0ZXJpbmcuIFRoZSBub3JtYWxpemVkIGRhdGEgd2FzIGZ1cnRoZXIgc2NhbGVkIHRvIHJlbW92ZSB0aGUgZWZmZWN0IG9mIG5VTUkgYW5kIHBlcmNlbnQubWl0byBmb3IgZnVydGhlciBjZWxsIGNsdXN0ZXJpbmcgcHVycG9zZS4KCmBgYHtyIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTV9CiMgbG9hZCBpbiB0aGUgbm9ybWFsaXplZCB1bmltcHV0ZWQgZGF0YQpkYXRhLmZpbGVwYXRoPC1maWxlLnBhdGgoaG9tZS5kaXIsInNjUk5BLXNlcS9TZXVyYXQvMV9ub3JtYWxpemVkX2RhdGFfdW5pbXB1dGVkXzEucmRzIikKb3V0cHV0LmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY1JOQS1zZXEvU2V1cmF0IikKCmlmKGZpbGUuZXhpc3RzKG91dHB1dC5kaXIpPT1GKXsKICBkaXIuY3JlYXRlKG91dHB1dC5kaXIpCn0KCmdjcWwxPC1yZWFkUkRTKGZpbGUgPSBkYXRhLmZpbGVwYXRoLCByZWZob29rID0gTlVMTCkKZ2NxbDE8LUZpbmRWYXJpYWJsZUZlYXR1cmVzKGdjcWwxLHNlbGVjdGlvbi5tZXRob2Q9InZzdCIsbmZlYXR1cmVzPWZlYXR1cmVzLm4pCgojIElkZW50aWZ5IHRoZSAxMCBtb3N0IGhpZ2hseSB2YXJpYWJsZSBnZW5lcwp0b3AxMCA8LSBoZWFkKFZhcmlhYmxlRmVhdHVyZXMoZ2NxbDEpLCAxMCkKCiMgcGxvdCB2YXJpYWJsZSBmZWF0dXJlcyB3aXRoIGFuZCB3aXRob3V0IGxhYmVscwpwbG90MSA8LSBWYXJpYWJsZUZlYXR1cmVQbG90KGdjcWwxKQpwbG90MiA8LSBMYWJlbFBvaW50cyhwbG90ID0gcGxvdDEsIHBvaW50cyA9IHRvcDEwLCByZXBlbCA9IFRSVUUpCkNvbWJpbmVQbG90cyhwbG90cyA9IGxpc3QocGxvdDEsIHBsb3QyKSkKYGBgCgoKVGhlbiB3ZSBzY2FsZSB0aGUgZGF0YSB0byByZW1vdmUgdGhlIGVmZmVjdCBmcm9tIG5VTUkgYW5kIHBlcmNlbnQubWl0by4gSGVyZSB3ZSBkZWNpZGUgdG8gc2NhbGUgZm9yIGFsbCBnZW5lcyBzbyB0aGF0IGxhdGVyIG9uLCB0aGUgaGVhdG1hcCB3aWxsIGJlIGdlbmVyYXRlZCB1c2luZyB0aGUgc2NhbGVkIGRhdGEuIApgYGB7ciByZXN1bHRzPSdoaWRlJ30KI21heWJlIHJlZ3Jlc3MgY2VsbCBjeWNsZSBodHRwOi8vc2F0aWphbGFiLm9yZy9zZXVyYXQvY2VsbF9jeWNsZV92aWduZXR0ZS5odG1sCmFsbC5nZW5lczwtcm93bmFtZXMoZ2NxbDEpCiNnY3FsMSA8LSBTY2FsZURhdGEoZ2NxbDEsZmVhdHVyZXMgPSBhbGwuZ2VuZXMsIHZhcnMudG8ucmVncmVzcyA9IGMoIm5Db3VudF9STkEiLCAicGVyY2VudC5tdCIpKQpnY3FsMSA8LSBTY2FsZURhdGEoZ2NxbDEpCiNvdXRwdXQuZmlsZXBhdGg8LWZpbGUucGF0aChvdXRwdXQuZGlyLCIyX3NjYWxlZF9hbGxnZW5lc191bmltcHV0ZWQucmRzIikKI3NhdmVSRFMoZ2NxbDEsIGZpbGUgPSBvdXRwdXQuZmlsZXBhdGgpCmBgYAoKCldlIHBlcmZvcm0gdGhlIHByaW5jaXBhbCBjb21wb25lbnQgYW5hbHlzaXMgb24gdGhlIHZhcmlhYmxlIGdlbmVzIHRvIGRlY2lkZSB0aGUgbnVtYmVyIG9mIFBDcyB0byB1c2UgZm9yIGNsdXN0ZXJpbmcuCmBgYHtyfQpnY3FsMSA8LSBSdW5QQ0Eob2JqZWN0ID0gZ2NxbDEsIGZlYXR1cmVzID0gVmFyaWFibGVGZWF0dXJlcyhnY3FsMSksIG5wY3M9MjAwLHZlcmJvc2U9RikKYGBgCgoKVG8gc2VlIGlmIHRoZXJlIGFyZSBQQ3MgdGhhdCBhcmUgc3ViamVjdCBzcGVjaWZpYywgdGhlIDJEIFBDQSBwbG90cyB1c2luZyB0aGUgdG9wIDMgUENzIGFyZSBhcyBmb2xsb3dzIGZvciB0aGUgdW4taW1wdXRlZCBkYXRhLiAKYGBge3IgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTUsY2FjaGU9RkFMU0UscmVzdWx0cz0naGlkZSd9CiNwYXIobWZyb3c9YygyLDIpKQpmaWcuMTwtRGltUGxvdChvYmplY3QgPSBnY3FsMSwgZGltcyA9IGMoMSwyKSwgcHQuc2l6ZT0wLjUpCmZpZy4yPC1EaW1QbG90KG9iamVjdCA9IGdjcWwxLCBkaW1zID0gYygyLDMpLCBwdC5zaXplPTAuNSkKZmlnLjM8LURpbVBsb3Qob2JqZWN0ID0gZ2NxbDEsIGRpbXMgPSBjKDEsMyksIHB0LnNpemU9MC41KQpncmlkLmFycmFuZ2UoZmlnLjEsZmlnLjIsZmlnLjMsbmNvbD0xKQpgYGAKCldlIGRyYXcgdGhlIGhlYXRtYXAgb2YgdGhlIHRvcCBnZW5lcyBmb3IgZWFjaCBQQy4KYGBge3IgZmlnLndpZHRoPTE1LGhlaWdodD04MCxjYWNoZT1GQUxTRX0KRGltSGVhdG1hcChnY3FsMSwgZGltcyA9IDE6MTAsIGNlbGxzID0gNTAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYApgYGB7ciBmaWcud2lkdGg9MTUsaGVpZ2h0PTgwLGNhY2hlPUZBTFNFfQpEaW1IZWF0bWFwKGdjcWwxLCBkaW1zID0gMTE6MjAsIGNlbGxzID0gNTAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYAoKYGBge3IgZmlnLndpZHRoPTE1LGhlaWdodD04MCxjYWNoZT1GQUxTRX0KRGltSGVhdG1hcChnY3FsMSwgZGltcyA9IDIxOjMwLCBjZWxscyA9IDUwMCwgYmFsYW5jZWQgPSBUUlVFKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xNSxoZWlnaHQ9ODAsY2FjaGU9RkFMU0V9CkRpbUhlYXRtYXAoZ2NxbDEsIGRpbXMgPSAzMTo0MCwgY2VsbHMgPSA1MDAsIGJhbGFuY2VkID0gVFJVRSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTUsaGVpZ2h0PTgwLGNhY2hlPUZBTFNFfQpEaW1IZWF0bWFwKGdjcWwxLCBkaW1zID0gNDE6NTAsIGNlbGxzID0gNTAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYAoKYGBge3IgZmlnLndpZHRoPTE1LGhlaWdodD04MCxjYWNoZT1GQUxTRX0KRGltSGVhdG1hcChnY3FsMSwgZGltcyA9IDUxOjYwLCBjZWxscyA9IDUwMCwgYmFsYW5jZWQgPSBUUlVFKQpgYGAKCmBgYHtyIGZpZy53aWR0aD0xNSxoZWlnaHQ9ODAsY2FjaGU9RkFMU0V9CkRpbUhlYXRtYXAoZ2NxbDEsIGRpbXMgPSA2MTo3MCwgY2VsbHMgPSA1MDAsIGJhbGFuY2VkID0gVFJVRSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTUsaGVpZ2h0PTgwLGNhY2hlPUZBTFNFfQpEaW1IZWF0bWFwKGdjcWwxLCBkaW1zID0gNzE6ODAsIGNlbGxzID0gNTAwLCBiYWxhbmNlZCA9IFRSVUUpCmBgYAoKV2UgdXNlIHRoZSBmb2xsb3dpbmcgSmFja3N0cmF3IHBsb3RzIGFuZCB0aGUgRWxib3dwbG90IHRvIGRlY2lkZSB0aGUgbnVtYmVyIG9mIFBDcyB0byB1c2UgZm9yIGRvd25zdHJlYW0gYW5hbHlzaXMuCmBgYHtyIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTQwLHJlc3VsdHM9J2hvbGQnfQpnY3FsMSA8LSBKYWNrU3RyYXcob2JqZWN0ID0gZ2NxbDEsIG51bS5yZXBsaWNhdGUgPSAxMDAsIHZlcmJvc2UgPSBUUlVFLGRpbXM9MjAwKQpnY3FsMSA8LSBTY29yZUphY2tTdHJhdyhnY3FsMSwgZGltcyA9IDE6MjAwKQpgYGAKCgpgYGB7ciBmaWcud2lkdGg9MTYsZmlnLmhlaWdodD04LGNhY2hlPUZBTFNFLHJlc3VsdHM9J2hpZGUnfQpKYWNrU3RyYXdQbG90KGdjcWwxLCBkaW1zID0gMToxMDApCmBgYAoKYGBge3IgZmlnLndpZHRoPTgsZmlnLmhlaWdodD04LGNhY2hlPUZBTFNFLHJlc3VsdHM9J2hpZGUnfQpFbGJvd1Bsb3Qob2JqZWN0ID0gZ2NxbDEsbmRpbXM9MTAwKQpgYGAKCmBgYHtyfQpwYy5udW08LTQ2CmBgYAoKQmFzZWQgb24gdGhlIEphY2tTdHJhdyBwbG90IGFuZCB0aGUgRWxib3cgcGxvdCwgd2UgZGVjaWRlZCB0byB1c2UgdGhlIHRvcCA0NiBQQ3MgZm9yIHRoZSB1bmltcHV0ZWQgZGF0YS4KCmBgYHtyfQpzYXZlUkRTKGdjcWwxLCBmaWxlID0gZmlsZS5wYXRoKG91dHB1dC5kaXIscGFzdGUoIjJfZGltcmVkdWN0aW9uX2RhdGFfIixsZW5ndGgoZ2NxbDFAYXNzYXlzJFJOQUB2YXIuZmVhdHVyZXMpLCJ2YXJnZW5lc19waXBlbGluZTEucmRzIixzZXA9IiIpKSkKIyB3ZSBzYXZlIHRoZSBmaWxlIGFzIHByZXByb2Nlc3NlZF9kYXRhX3NhdmVyLnJkcyB0byBwcmV2ZW50IGFueSBtaXN0YWtlcyBidXQgd2UgY2hhbmdlIHRoZSBmaWxlIG5hbWUgYmFjayB0byBwcmVwcm9jZXNzZWRfZGF0YS5yZHMgYmVmb3JlIHBlcmZvcm1pbmcgZG93bnN0cmVhbSBhbmFseXNpcy4KCmNhdCgiV2Ugc2F2ZSB0aGUgUiBwcm9qZWN0IGFzXG4iKQpjYXQoInVuaW1wdXRlZCBkYXRhOlx0IixmaWxlLnBhdGgob3V0cHV0LmRpcixwYXN0ZSgiMl9kaW1yZWR1Y3Rpb25fZGF0YV8iLGxlbmd0aChnY3FsMUBhc3NheXMkUk5BQHZhci5mZWF0dXJlcyksInZhcmdlbmVzX3BpcGVsaW5lMS5yZHMiLHNlcD0iIikpLCJcbiIsc2VwPSIiKQpgYGAKCiMjIyBEYXRhIHZpc3VhbGl6YXRpb24KCiMjIyMgVU1BUApUbyBpZGVudGlmeSBwb3RlbnRpYWwgb3V0bHlpbmcgY2VsbHMgYW5kIHNhbXBsZXMsIHdlIGZpcnN0IGxhYmVsIHRoZSB0U05FIHBsb3QgdXNpbmcgZGlzZWFzZV9zYW1wbGVuYW1lcy4KYGBge3J9CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIwojIDIuIGdlbmVyYXRlIHRoZSBUU05FIHBsb3QgbGFiZWxsZWQgc3BlY2lmaWNhbGx5IGZvciBlYWNoIHNhbXBsZSBzZXBhcmF0ZWx5IHRvIGxvb2sgZm9yIG91dGxpZXJzLgojIHNydW4gLS1wdHkgLS14MTEgLXAgcGlfa2FtaW5za2kgLXQgMTI6MDA6MDAgLS1udGFza3M9MSAtLW5vZGVzPTEgLS1jcHVzLXBlci10YXNrPTEgLS1tZW09NjAxNTIgYmFzaAojbW9kdWxlIGxvYWQgQXBwcy9SLzMuNC4xLWdlbmVyaWMKI21vZHVsZSBsb2FkIEFwcHMvUi8zLjQuMy1nZW5lcmljCgpkYXRhLmZpbGVwYXRoPC1maWxlLnBhdGgob3V0cHV0LmRpciwiMl9kaW1yZWR1Y3Rpb25fZGF0YV8yMDAwdmFyZ2VuZXNfcGlwZWxpbmUxLnJkcyIpCmdjcWwxIDwtIHJlYWRSRFMoZmlsZSA9IGRhdGEuZmlsZXBhdGgsIHJlZmhvb2sgPSBOVUxMKQoKIyBnZW5lcmF0ZSB0aGUgVFNORSBwbG90IGxhYmVsZWQgYnkgbGFiZWxpbmcgc2FtcGxlcyB3aXRoIGRpZmZlcmVudCBjb2xvcnMgCnNhbXBsZTE8LUZpbmROZWlnaGJvcnMoZ2NxbDEsZGltcz0xOnBjLm51bSkKc2FtcGxlMTwtRmluZENsdXN0ZXJzKHNhbXBsZTEscmVzb2x1dGlvbj0xLjIpCnNhbXBsZTE8LVJ1blVNQVAoc2FtcGxlMSxkaW1zPTE6cGMubnVtKQojc2FtcGxlMTwtUnVuVU1BUChzYW1wbGUxLGRpbXM9MTpwYy5udW0sdW1hcC5tZXRob2Q9InVtYXAtbGVhcm4iKQoKCmBgYAoKYGBge3IgZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MjAsZmlnLmNhcD0iVU1BUCBsYWJlbGVkIGJ5IGEpLiBjZWxsIGNsdXN0ZXJzLCBiKS4gc2FtcGxlIG5hbWVzIGFuZCBjKS4gZGlzZWFzZSBmb3IgdGhlIHVuaW1wdXRlZCBkYXRhLiIsY2FjaGU9RkFMU0UscmVzdWx0cz0naGlkZSd9CmZpZzE8LURpbVBsb3Qoc2FtcGxlMSxyZWR1Y3Rpb249InVtYXAiKQpmaWcyPC1EaW1QbG90KHNhbXBsZTEscmVkdWN0aW9uPSJ1bWFwIixncm91cC5ieT0iZGlzZWFzZS5zYW1wbGVuYW1lcyIpCmZpZzM8LURpbVBsb3Qoc2FtcGxlMSxyZWR1Y3Rpb249InVtYXAiLGdyb3VwLmJ5PSJkaXNlYXNlIikKZ3JpZC5hcnJhbmdlKGZpZzEsZmlnMixmaWczLG5jb2w9MSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0yMCxmaWcuY2FwPSJVTUFQIGxhYmVsZWQgYnkgYSkuIGNlbGwgY2x1c3RlcnMsIGIpLiBzYW1wbGUgbmFtZXMgYW5kIGMpLiBkaXNlYXNlIGZvciB0aGUgdW5pbXB1dGVkIGRhdGEuIixjYWNoZT1GQUxTRSxyZXN1bHRzPSdoaWRlJ30KcGMubnVtPC00NgojIGdlbmVyYXRlIHRoZSBUU05FIHBsb3QgbGFiZWxlZCBieSBsYWJlbGluZyBzYW1wbGVzIHdpdGggZGlmZmVyZW50IGNvbG9ycyAKc2FtcGxlMTwtRmluZE5laWdoYm9ycyhnY3FsMSxkaW1zPTE6cGMubnVtKQpzYW1wbGUxPC1GaW5kQ2x1c3RlcnMoc2FtcGxlMSxyZXNvbHV0aW9uPTEuMikKc2FtcGxlMTwtUnVuVU1BUChzYW1wbGUxLGRpbXM9MTpwYy5udW0pCiNzYW1wbGUxPC1SdW5VTUFQKHNhbXBsZTEsZGltcz0xOnBjLm51bSx1bWFwLm1ldGhvZD0idW1hcC1sZWFybiIpCmZpZzE8LURpbVBsb3Qoc2FtcGxlMSxyZWR1Y3Rpb249InVtYXAiKQpmaWcyPC1EaW1QbG90KHNhbXBsZTEscmVkdWN0aW9uPSJ1bWFwIixncm91cC5ieT0iZGlzZWFzZS5zYW1wbGVuYW1lcyIpCmZpZzM8LURpbVBsb3Qoc2FtcGxlMSxyZWR1Y3Rpb249InVtYXAiLGdyb3VwLmJ5PSJkaXNlYXNlIikKZ3JpZC5hcnJhbmdlKGZpZzEsZmlnMixmaWczLG5jb2w9MSkKYGBgCiMjIyMgdFNORSBwbG90CgpgYGB7ciBldmFsPUZBTFNFfQpwYy5udW08LTM1CnNhbXBsZTEgPC0gUnVuVFNORShzYW1wbGUxLCBkaW1zPSAxOnBjLm51bSkKYGBgCgpgYGB7ciBmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0yMCxmaWcuY2FwPSJ0U05FIHBsb3RzIGxhYmVsZWQgYnkgYSkuIGNlbGwgY2x1c3RlcnMsIGIpLiBzYW1wbGUgbmFtZXMgYW5kIGMpLiBkaXNlYXNlIGZvciB0aGUgdW5pbXB1dGVkIGRhdGEuIixjYWNoZT1GQUxTRSxyZXN1bHRzPSdoaWRlJ30KZmlnMTwtRGltUGxvdChzYW1wbGUxLHJlZHVjdGlvbj0idHNuZSIpCmZpZzI8LURpbVBsb3Qoc2FtcGxlMSxyZWR1Y3Rpb249InRzbmUiLGdyb3VwLmJ5PSJkaXNlYXNlLnNhbXBsZW5hbWVzIikKZmlnMzwtRGltUGxvdChzYW1wbGUxLHJlZHVjdGlvbj0idHNuZSIsZ3JvdXAuYnk9ImRpc2Vhc2UiKQpncmlkLmFycmFuZ2UoZmlnMSxmaWcyLGZpZzMsbmNvbD0xKQpgYGAKCiMjIyMgZGF0YSBzYXZpbmcKYGBge3J9CnNhdmVSRFMoc2FtcGxlMSwgZmlsZSA9IGZpbGUucGF0aChvdXRwdXQuZGlyLHBhc3RlKCIyX3Zpc3VhbGl6YXRpb25fZGF0YV8iLGxlbmd0aChnY3FsMUBhc3NheXMkUk5BQHZhci5mZWF0dXJlcyksInZhcmdlbmVzX3BpcGVsaW5lMS5yZHMiLHNlcD0iIikpKQpjYXQoIldlIHNhdmVkIHRoZSBzZXVyYXQgb2JqZWN0IHdpdGggVU1BUCBhbmQgdFNORSBhcyAiLGZpbGUucGF0aChvdXRwdXQuZGlyLHBhc3RlKCIyX3Zpc3VhbGl6YXRpb25fZGF0YV8iLGxlbmd0aChzYW1wbGUxQGFzc2F5cyRSTkFAdmFyLmZlYXR1cmVzKSwidmFyZ2VuZXNfcGlwZWxpbmUxLnJkcyIsc2VwPSIiKSksIlxuIixzZXA9IiIpCmBgYAoKIyMjIENlbGwgVHlwaW5nCgpgYGB7cn0KbXkuZGlzdGluY3QuY29sb3JzPC1jKCcjZTYxOTRiJywgJyMzY2I0NGInLCAnI2ZmZTExOScsICcjNDM2M2Q4JywgJyNmNTgyMzEnLCAnIzkxMWViNCcsICcjNDZmMGYwJywgJyNmMDMyZTYnLCAnI2JjZjYwYycsICcjZmFiZWJlJywgJyMwMDgwODAnLCAnI2U2YmVmZicsICcjOWE2MzI0JywgJyNmZmZhYzgnLCAnIzgwMDAwMCcsICcjYWFmZmMzJywgJyM4MDgwMDAnLCAnI2ZmZDhiMScsICcjMDAwMDc1JywgJyM4MDgwODAnLCAnI2ZmZmZmZicsICcjMDAwMDAwJykKYGBgCgpUbyBkZXNjcmliZSB0aGUgY2VsbCB0eXBlcyBjYXB0dXJlZCBpbiB0aGUgZGF0YSwgdGhlIGludGVncmF0ZWQgYW5hbHlzaXMgbWF5IHByb3ZpZGUgY2xlYW5lciByZXN1bHRzLiBUaGVyZWZvcmUsIHdlIGNvbmR1Y3QgdGhlIGludGVncmF0ZWQgYW5hbHlzaXMgb2YgYWxsIHRoZSBhc3RobWEgMTBYIGRhdGEgdXNpbmcgU2V1cmF0IFYzLjEgaW4gdGhpcyBub3RlIHRvIHJlZHVjZSB0aGUgc3ViamVjdCBlZmZlY3QgaW4gdGhlIGRhdGEgdmlzdWxpemF0aW9uIGFuZCB0aGUgdHJhamVjdG9yeSBhbmFseXNpcyBhdCBsZWFzdC4gCgojIyMjIERhdGEgTG9hZGluZwpgYGB7ciBkYXRhX2xvYWRpbmcscmVzdWx0cz0naG9sZCd9CiMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjCiMgMS4gbG9hZCBpbiB0aGUgc2luZ2xlciBvYmplY3QKaG9tZS5kaXI8LSIvaG9tZS95YW54aXRpbmcvRG9jdW1lbnRzL1Jlc2VhcmNoL0dSQURTX1NBUkNfUEJNQyIKZGF0YS5kaXI8LWZpbGUucGF0aChob21lLmRpciwic2NSTkEtc2VxL1NldXJhdCIpCnNhbXBsZTE8LXJlYWRSRFMoZmlsZS5wYXRoKGRhdGEuZGlyLCIyX3Zpc3VhbGl6YXRpb25fZGF0YV8yMDAwdmFyZ2VuZXNfcGlwZWxpbmUxLnJkcyIpLHJlZmhvb2sgPSBOVUxMKQoKc2FtcGxlMS5yYXc8LXNhbXBsZTEKYGBgCgojIyMjIGludGVncmF0aW5nIGRhdGEKClNwbGl0IHRoZSB3aG9sZSBkYXRhc2V0IGJhc2VkIG9uIHN1YmplY3QgSUQgKGZyZXNoIGFuZCBEU01PIHNhbXBsZXMgZnJvbSB0aGUgc2FtZSBzdWJqZWN0IGFyZSBjb25zaWRlcmVkIGFzIG9uZSBzYW1wbGUpLiBGaW5kIGFuY2hvcnMgdG8gaW50ZWdyYXRlIGFsbCB0aGUgZGF0YXNldHMgdG9nZXRoZXIuCmBgYHtyfQpzYW1wbGUxLmxpc3Q8LVNwbGl0T2JqZWN0KHNhbXBsZTEucmF3LHNwbGl0LmJ5PSJzYW1wbGUubmFtZXMiKQpzYW1wbGUxLmxpc3Q8LWxhcHBseShYPXNhbXBsZTEubGlzdCxGVU49ZnVuY3Rpb24oeCl7CiAgeDwtTm9ybWFsaXplRGF0YSh4KQogIHg8LUZpbmRWYXJpYWJsZUZlYXR1cmVzKHgsc2VsZWN0aW9uLm1ldGhvZD0idnN0IixuZmVhdHVyZXM9MjAwMCkKfSkKdGVtcC5jZWxsbnVtPC11bmxpc3QobGFwcGx5KHNhbXBsZTEubGlzdCxuY29sKSkKI3NhbXBsZTEubGlzdDwtc2FtcGxlMS5saXN0W3RlbXAuY2VsbG51bT49NDBdCiMgaW5jcmVhc2UgdGhlIGdsb2JhbCBtYXhTaXplCm9wdGlvbnMoZnV0dXJlLmdsb2JhbHMubWF4U2l6ZT0gNDE5NDMwNDAwMCkKZmVhdHVyZXMgPC0gU2VsZWN0SW50ZWdyYXRpb25GZWF0dXJlcyhvYmplY3QubGlzdCA9IHNhbXBsZTEubGlzdCkKI3NhbXBsZTEubGlzdCA8LSBsYXBwbHkoWCA9IHNhbXBsZTEubGlzdCwgRlVOID0gZnVuY3Rpb24oeCkgewojICAgIHggPC0gU2NhbGVEYXRhKHgsIGZlYXR1cmVzID0gZmVhdHVyZXMsIHZlcmJvc2UgPSBGQUxTRSkKIyAgICB4IDwtIFJ1blBDQSh4LCBmZWF0dXJlcyA9ZmVhdHVyZXMsIG5wY3M9NDAsdmVyYm9zZT1GKQojfSkKI3NhbXBsZTEuYW5jaG9yczwtRmluZEludGVncmF0aW9uQW5jaG9ycyhzYW1wbGUxLmxpc3RbdGVtcC5jZWxsbnVtPj05Ml0say5maWx0ZXI9OTIsZGltcz0xOjQwLHZlcmJvc2U9RkFMU0UpCnNhbXBsZTEuYW5jaG9yczwtRmluZEludGVncmF0aW9uQW5jaG9ycyhvYmplY3QubGlzdD1zYW1wbGUxLmxpc3RbdGVtcC5jZWxsbnVtPj03MDBdLGFuY2hvci5mZWF0dXJlcz1mZWF0dXJlcywgcmVkdWN0aW9uPSJycGNhIixrLmZpbHRlcj00MCxkaW1zPTE6NDAsdmVyYm9zZT1GQUxTRSkKI3NhbXBsZTEuY29tYmluZWQ8LUludGVncmF0ZURhdGEoYW5jaG9yc2V0PXNhbXBsZTEuYW5jaG9ycyxmZWF0dXJlcy50by5pbnRlZ3JhdGU9cm93bmFtZXMoc2FtcGxlMS5yYXdAYXNzYXlzJFJOQUBjb3VudHMpLGRpbXM9MTo0MCx2ZXJib3NlPUZBTFNFKQojLGsuZmlsdGVyPTQwLGRpbXM9MTo0MApzYW1wbGUxLmNvbWJpbmVkPC1JbnRlZ3JhdGVEYXRhKGFuY2hvcnNldD1zYW1wbGUxLmFuY2hvcnMsdmVyYm9zZT1GQUxTRSxrLndlaWdodCA9IDQ1KQpgYGAKCgpgYGB7cn0Kb3V0cHV0LmRpcjwtZmlsZS5wYXRoKGhvbWUuZGlyLCJzY1JOQS1zZXEvU2V1cmF0IikKZGlyLmNyZWF0ZShmaWxlLnBhdGgob3V0cHV0LmRpciwiaW50ZWdyYXRlZF9hbmFseXNpcyIpKQpvdXRwdXQuZmlsZXBhdGg8LWZpbGUucGF0aChvdXRwdXQuZGlyLCJpbnRlZ3JhdGVkX2FuYWx5c2lzIiwic2V1cmF0X29iamVjdF9pbnRlZ3JhdGVkX2FsbGNlbGxzLnJkcyIpCnNhdmVSRFMoc2FtcGxlMS5jb21iaW5lZCxmaWxlPW91dHB1dC5maWxlcGF0aCkKY2F0KCJXZSBzYXZlZCB0aGUgaW50ZWdyYXRlZCBkYXRhIGZvciBhbGwgY2VsbHMgYXMgIixvdXRwdXQuZmlsZXBhdGgsIlxuIixzZXA9IiIpCgpvdXRwdXQuZmlsZXBhdGg8LWZpbGUucGF0aChvdXRwdXQuZGlyLCJpbnRlZ3JhdGVkX2FuYWx5c2lzIiwic2V1cmF0X29iamVjdF9vcmlnaW5hbF9hbGxjZWxscy5yZHMiKQpzYXZlUkRTKHNhbXBsZTEucmF3LGZpbGU9b3V0cHV0LmZpbGVwYXRoKQpjYXQoIldlIHNhdmVkIHRoZSBvcmlnaW5hbCBkYXRhIGZvciBhbGwgY2VsbHMgYXMgIixvdXRwdXQuZmlsZXBhdGgsIlxuIixzZXA9IiIpCmBgYAoKQ2x1c3RlciB0aGUgY2VsbHMgdXNpbmcgdGhlIGludGVncmF0ZWQgZGF0YS4KCmBgYHtyfQpEZWZhdWx0QXNzYXkoc2FtcGxlMS5jb21iaW5lZCkgPC0gImludGVncmF0ZWQiCiMgUnVuIHRoZSBzdGFuZGFyZCB3b3JrZmxvdyBmb3IgdmlzdWFsaXphdGlvbiBhbmQgY2x1c3RlcmluZwpzYW1wbGUxLmNvbWJpbmVkIDwtIFNjYWxlRGF0YShzYW1wbGUxLmNvbWJpbmVkLCB2ZXJib3NlID0gRkFMU0UpCnNhbXBsZTEuY29tYmluZWQgPC0gUnVuUENBKHNhbXBsZTEuY29tYmluZWQsIG5wY3MgPSA0MCwgdmVyYm9zZSA9IEZBTFNFKQojIHQtU05FIGFuZCBDbHVzdGVyaW5nCnNhbXBsZTEuY29tYmluZWQgPC0gUnVuVU1BUChzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6NDApCnNhbXBsZTEuY29tYmluZWQgPC0gRmluZE5laWdoYm9ycyhzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6NDApCnNhbXBsZTEuY29tYmluZWQgPC0gRmluZENsdXN0ZXJzKHNhbXBsZTEuY29tYmluZWQsIHJlc29sdXRpb24gPSAxLjIpCmBgYAoKV2Ugc2F2ZSB0aGUgcmVzdWx0IGluIGEgcmRzIGZpbGUuCmBgYHtyfQpvdXRwdXQuc3ViZGlyPC1maWxlLnBhdGgob3V0cHV0LmRpciwiaW50ZWdyYXRlZF9hbmFseXNpcyIpCmlmKGZpbGUuZXhpc3RzKG91dHB1dC5zdWJkaXIpPT1GKXsKICBkaXIuY3JlYXRlKG91dHB1dC5zdWJkaXIpCn0KCm91dHB1dC5maWxlcGF0aDwtZmlsZS5wYXRoKG91dHB1dC5zdWJkaXIsInNldXJhdF9vYmplY3RfaW50ZWdyYXRlZF9hbGxjZWxsc19jZWxsY2x1c3RlcmluZ19ub1NpbmdsZVIucmRzIikKc2F2ZVJEUyhzYW1wbGUxLmNvbWJpbmVkLGZpbGU9b3V0cHV0LmZpbGVwYXRoKQpjYXQoIldlIHNhdmVkIHRoZSBzZXVyYXQgb2JqZWN0IG9mIHRoZSBpbnRlZ3JhdGVkIGRhdGEgd2l0aCBjZWxsIGNsdXN0ZXJpbmcgcmVzdWx0cyBvbmx5IGFzICIsb3V0cHV0LmZpbGVwYXRoLCJcbiIsc2VwPSIiKQpgYGAKCgojIyMjIFZpc3VhbGl6aW5nIGludGVncmF0ZWQgZGF0YQoKYGBge3IsZmlnLndpZHRoPTgsZmlnLmhlaWdodD0yMH0KbGlicmFyeShjb3dwbG90KQpwMSA8LSBEaW1QbG90KHNhbXBsZTEuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnkgPSAic2FtcGxlLm5hbWVzIikKcDIgPC0gRGltUGxvdChzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gVFJVRSkKI3AzPC1EaW1QbG90KHNhbXBsZTEuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnk9InNpbmdsZXIuaHBjYS5jbHVzdGVyLm1lcmdlZCIsIGxhYmVsID0gVFJVRSxjb2xzPW15LmRpc3RpbmN0LmNvbG9ycykKI3A0PC1EaW1QbG90KHNhbXBsZTEuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnk9InNpbmdsZXIuaHBjYS5jbHVzdGVyIiwgbGFiZWwgPSBUUlVFLGNvbHM9bXkuZGlzdGluY3QuY29sb3JzKQpwNTwtRGltUGxvdChzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5PSJkaXNlYXNlIiwgbGFiZWwgPSBUUlVFLGNvbHM9bXkuZGlzdGluY3QuY29sb3JzKQojcGxvdF9ncmlkKHAyLCBwMSwgcDMscDQscDUsbmNvbD0xKQpwbG90X2dyaWQocDIsIHAxLHA1LG5jb2w9MSkKYGBgCgpgYGB7cixmaWcud2lkdGg9NCxmaWcuaGVpZ2h0PTYwfQojIGZpbmQgb3V0IHRoZSBzYW1wbGVzIHdpdGggc21hbGwgbnVtYmVyIG9mIGNlbGxzIGFuZCB0cnkgdG8gc2VlIHdoZXJlIHRoZXkgYXJlIG9uIHRoZSBVTUFQCnRlbXAuY2VsbG51bTwtdGFibGUoc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEkc2FtcGxlLm5hbWVzKQp0ZW1wLm5hbWVzPC1uYW1lcyh0ZW1wLmNlbGxudW0pCnRlbXAuY2VsbG51bTwtYXMubnVtZXJpYyh0ZW1wLmNlbGxudW0pCm5hbWVzKHRlbXAuY2VsbG51bSk8LXRlbXAubmFtZXMKdGVtcC5zYW1wbGVzPC1uYW1lcyh0ZW1wLmNlbGxudW0pW3RlbXAuY2VsbG51bTw9MTQwMDBdCnRlbXAuc2FtcGxlczwtbmFtZXMoc29ydCh0ZW1wLmNlbGxudW1bdGVtcC5zYW1wbGVzXSxkZWNyZWFzaW5nPUYpKQoKZmlnLmxpc3Q8LWxpc3QoKQpmb3IoaSBpbiAxOmxlbmd0aCh0ZW1wLnNhbXBsZXMpKXsKIyAgdGVtcC5jb2xvcjwtcmVwKCJncmV5Iixucm93KHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhKSkKIyAgdGVtcC5jb2xvcltzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSRzYW1wbGUubmFtZXM9PXRlbXAuc2FtcGxlc1tpXV08LSJyZWQiCiAgZmlnLmxpc3RbW2ldXTwtRGltUGxvdChzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsY2VsbHMuaGlnaGxpZ2h0PXJvd25hbWVzKHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhKVtzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSRzYW1wbGUubmFtZXM9PXRlbXAuc2FtcGxlc1tpXV0sc2l6ZXMuaGlnaGxpZ2h0PTAuMikrbGFicyh0aXRsZT1wYXN0ZTAodGVtcC5zYW1wbGVzW2ldLCI6Iix0ZW1wLmNlbGxudW1bdGVtcC5zYW1wbGVzW2ldXSkpCn0KCiNkby5jYWxsKCJncmlkLmFycmFuZ2UiLCBjKGZpZy5saXN0LCBucm93PWxlbmd0aChmaWcubGlzdCkpKQpvdXRwdXQuZmlsZXBhdGg8LWZpbGUucGF0aChvdXRwdXQuZGlyLCJ1bWFwX2ludGVncmF0ZWRfY291cnRuZXlfcGVyc2FtcGxlLnBkZiIpCgpnZ3NhdmUoCiAgIGZpbGVuYW1lID0gb3V0cHV0LmZpbGVwYXRoLCAKICAgcGxvdCA9IG1hcnJhbmdlR3JvYihmaWcubGlzdCwgbnJvdz0xLCBuY29sPTEpLCAKICAgd2lkdGggPSA2LCBoZWlnaHQgPSA2CikKCmNhdCgiVGhlIFVNQVAgb2YgZWFjaCBzYW1wbGUgaGlnaGxpZ2h0ZWQgaGFzIGJlZW4gc2F2ZWQgYXMgIixvdXRwdXQuZmlsZXBhdGgsIlxuIixzZXA9IiIpCgojcGRmKG91dHB1dC5maWxlcGF0aCwgb25lZmlsZSA9IFRSVUUpCiNmb3IgKGkgaW4gc2VxKGxlbmd0aChmaWcubGlzdCkpKSB7CiMgIGRvLmNhbGwoImdyaWQuYXJyYW5nZSIsIGZpZy5saXN0W1tpXV0pICAKI30KI2Rldi5vZmYoKQpgYGAKIyMjIyBJbnRlZ3JhdGUgdXNpbmcgQXppbXV0aAoKV2UgdXBsb2FkZWQgdGhlIHNldXJhdF9vYmplY3Rfb3JpZ2luYWxfYWxsY2VsbHMucmRzIG9udG8gQXppbXV0aCB3ZWJzaXRlIGFuZCBtYXAgdGhlbSB0byB0aGUgcmVmZXJlbmNlIGh1bWFuIFBCTUMgZGF0YS4gV2UgZG93bmxvYWRlZCB0aGUgcHJlZGljdGVkIGNlbGwgdHlwZXMgdG8gY29tcGFyZSB0byB0aGUgcHJlZGljdGlvbnMgYnkgQW15J3MgZGF0YS4KYGBge3IgZXZhbD1GQUxTRX0KIyB0aGVzZSBhcmUgY29kZXMgZG93bmxvYWRlZCBmcm9tIHRoZSBBemltdXRoIHdlYnNpdGUgYXMgYW5hbHlzaXMuUiBjb2RlcwojIS91c3IvYmluL2VudiBSc2NyaXB0CgojIEVuc3VyZSBTZXVyYXQgdjQuMCBvciBoaWdoZXIgaXMgaW5zdGFsbGVkCmlmIChwYWNrYWdlVmVyc2lvbihwa2cgPSAiU2V1cmF0IikgPCBwYWNrYWdlX3ZlcnNpb24oeCA9ICI0LjAuMCIpKSB7CiAgc3RvcCgiTWFwcGluZyBkYXRhc2V0cyByZXF1aXJlcyBTZXVyYXQgdjQgb3IgaGlnaGVyLiIsIGNhbGwuID0gRkFMU0UpCn0KCiMgRW5zdXJlIGdsbUdhbVBvaSBpcyBpbnN0YWxsZWQKaWYgKCFyZXF1aXJlTmFtZXNwYWNlKCJnbG1HYW1Qb2kiLCBxdWlldGx5ID0gVFJVRSkpIHsKICBpZiAoIXJlcXVpcmVOYW1lc3BhY2UoIkJpb2NNYW5hZ2VyIiwgcXVpZXRseSA9IFRSVUUpKSB7CiAgICBCaW9jTWFuYWdlcjo6aW5zdGFsbCgiZ2xtR2FtUG9pIikKICB9Cn0KCiMgRW5zdXJlIEF6aW11dGggaXMgaW5zdGFsbGVkCmlmIChwYWNrYWdlVmVyc2lvbihwa2cgPSAiQXppbXV0aCIpIDwgcGFja2FnZV92ZXJzaW9uKHggPSAiMC4zLjEiKSkgewogIHN0b3AoIlBsZWFzZSBpbnN0YWxsIGF6aW11dGggLSByZW1vdGVzOjppbnN0YWxsX2dpdGh1Yignc2F0aWphbGFiL2F6aW11dGgnKSIsIGNhbGwuID0gRkFMU0UpCn0KCmxpYnJhcnkoU2V1cmF0KQpsaWJyYXJ5KEF6aW11dGgpCgojIERvd25sb2FkIHRoZSBBemltdXRoIHJlZmVyZW5jZSBhbmQgZXh0cmFjdCB0aGUgYXJjaGl2ZQoKIyBMb2FkIHRoZSByZWZlcmVuY2UKIyBDaGFuZ2UgdGhlIGZpbGUgcGF0aCBiYXNlZCBvbiB3aGVyZSB0aGUgcmVmZXJlbmNlIGlzIGxvY2F0ZWQgb24geW91ciBzeXN0ZW0uCnJlZmVyZW5jZSA8LSBMb2FkUmVmZXJlbmNlKHBhdGggPSAiaHR0cHM6Ly9zZXVyYXQubnlnZW5vbWUub3JnL2F6aW11dGgvcmVmZXJlbmNlcy92MS4wLjAvaHVtYW5fcGJtYyIpCgojIExvYWQgdGhlIHF1ZXJ5IG9iamVjdCBmb3IgbWFwcGluZwojIENoYW5nZSB0aGUgZmlsZSBwYXRoIGJhc2VkIG9uIHdoZXJlIHRoZSBxdWVyeSBmaWxlIGlzIGxvY2F0ZWQgb24geW91ciBzeXN0ZW0uCnF1ZXJ5IDwtIExvYWRGaWxlSW5wdXQocGF0aCA9ICJzZXVyYXRfb2JqZWN0X29yaWdpbmFsX2FsbGNlbGxzLnJkcyIpCgojIENhbGN1bGF0ZSBuQ291bnRfUk5BIGFuZCBuRmVhdHVyZV9STkEgaWYgdGhlIHF1ZXJ5IGRvZXMgbm90CiMgY29udGFpbiB0aGVtIGFscmVhZHkKaWYgKCFhbGwoYygibkNvdW50X1JOQSIsICJuRmVhdHVyZV9STkEiKSAlaW4lIGMoY29sbmFtZXMoeCA9IHF1ZXJ5W1tdXSkpKSkgewogICAgY2FsY24gPC0gYXMuZGF0YS5mcmFtZSh4ID0gU2V1cmF0Ojo6Q2FsY04ob2JqZWN0ID0gcXVlcnkpKQogICAgY29sbmFtZXMoeCA9IGNhbGNuKSA8LSBwYXN0ZSgKICAgICAgY29sbmFtZXMoeCA9IGNhbGNuKSwKICAgICAgIlJOQSIsCiAgICAgIHNlcCA9ICdfJwogICAgKQogICAgcXVlcnkgPC0gQWRkTWV0YURhdGEoCiAgICAgIG9iamVjdCA9IHF1ZXJ5LAogICAgICBtZXRhZGF0YSA9IGNhbGNuCiAgICApCiAgICBybShjYWxjbikKfQoKIyBDYWxjdWxhdGUgcGVyY2VudCBtaXRvY2hvbmRyaWFsIGdlbmVzIGlmIHRoZSBxdWVyeSBjb250YWlucyBnZW5lcwojIG1hdGNoaW5nIHRoZSByZWd1bGFyIGV4cHJlc3Npb24gIl5NVC0iCmlmIChhbnkoZ3JlcGwocGF0dGVybiA9ICdeTVQtJywgeCA9IHJvd25hbWVzKHggPSBxdWVyeSkpKSkgewogIHF1ZXJ5IDwtIFBlcmNlbnRhZ2VGZWF0dXJlU2V0KAogICAgb2JqZWN0ID0gcXVlcnksCiAgICBwYXR0ZXJuID0gJ15NVC0nLAogICAgY29sLm5hbWUgPSAncGVyY2VudC5tdCcsCiAgICBhc3NheSA9ICJSTkEiCiAgKQp9CgojIEZpbHRlciBjZWxscyBiYXNlZCBvbiB0aGUgdGhyZXNob2xkcyBmb3IgbkNvdW50X1JOQSBhbmQgbkZlYXR1cmVfUk5BCiMgeW91IHNldCBpbiB0aGUgYXBwCmNlbGxzLnVzZSA8LSBxdWVyeVtbIm5Db3VudF9STkEiLCBkcm9wID0gVFJVRV1dIDw9IDY3MjY5ICYKICBxdWVyeVtbIm5Db3VudF9STkEiLCBkcm9wID0gVFJVRV1dID49IDQ1NyAmCiAgcXVlcnlbWyJuRmVhdHVyZV9STkEiLCBkcm9wID0gVFJVRV1dIDw9IDc3NjEgJgogIHF1ZXJ5W1sibkZlYXR1cmVfUk5BIiwgZHJvcCA9IFRSVUVdXSA+PSAzMDAKCiMgSWYgdGhlIHF1ZXJ5IGNvbnRhaW5zIG1pdG9jaG9uZHJpYWwgZ2VuZXMsIGZpbHRlciBjZWxscyBiYXNlZCBvbiB0aGUKIyB0aHJlc2hvbGRzIGZvciBwZXJjZW50Lm10IHlvdSBzZXQgaW4gdGhlIGFwcAppZiAoInBlcmNlbnQubXQiICVpbiUgYyhjb2xuYW1lcyh4ID0gcXVlcnlbW11dKSkpIHsKICBjZWxscy51c2UgPC0gY2VsbHMudXNlICYgKHF1ZXJ5W1sicGVyY2VudC5tdCIsIGRyb3AgPSBUUlVFXV0gPD0gMTAwICYKICAgIHF1ZXJ5W1sicGVyY2VudC5tdCIsIGRyb3AgPSBUUlVFXV0gPj0gMCkKfQoKIyBSZW1vdmUgZmlsdGVyZWQgY2VsbHMgZnJvbSB0aGUgcXVlcnkKcXVlcnkgPC0gcXVlcnlbLCBjZWxscy51c2VdCgojIFByZXByb2Nlc3Mgd2l0aCBTQ1RyYW5zZm9ybQpxdWVyeSA8LSBTQ1RyYW5zZm9ybSgKICBvYmplY3QgPSBxdWVyeSwKICBhc3NheSA9ICJSTkEiLAogIG5ldy5hc3NheS5uYW1lID0gInJlZkFzc2F5IiwKICByZXNpZHVhbC5mZWF0dXJlcyA9IHJvd25hbWVzKHggPSByZWZlcmVuY2UkbWFwKSwKICByZWZlcmVuY2UuU0NULm1vZGVsID0gcmVmZXJlbmNlJG1hcFtbInJlZkFzc2F5Il1dQFNDVE1vZGVsLmxpc3QkcmVmbW9kZWwsCiAgbWV0aG9kID0gJ2dsbUdhbVBvaScsCiAgbmNlbGxzID0gMjAwMCwKICBuX2dlbmVzID0gMjAwMCwKICBkby5jb3JyZWN0LnVtaSA9IEZBTFNFLAogIGRvLnNjYWxlID0gRkFMU0UsCiAgZG8uY2VudGVyID0gVFJVRQopCgojIEZpbmQgYW5jaG9ycyBiZXR3ZWVuIHF1ZXJ5IGFuZCByZWZlcmVuY2UKYW5jaG9ycyA8LSBGaW5kVHJhbnNmZXJBbmNob3JzKAogIHJlZmVyZW5jZSA9IHJlZmVyZW5jZSRtYXAsCiAgcXVlcnkgPSBxdWVyeSwKICBrLmZpbHRlciA9IE5BLAogIHJlZmVyZW5jZS5uZWlnaGJvcnMgPSAicmVmZHIuYW5ub3kubmVpZ2hib3JzIiwKICByZWZlcmVuY2UuYXNzYXkgPSAicmVmQXNzYXkiLAogIHF1ZXJ5LmFzc2F5ID0gInJlZkFzc2F5IiwKICByZWZlcmVuY2UucmVkdWN0aW9uID0gInJlZkRSIiwKICBub3JtYWxpemF0aW9uLm1ldGhvZCA9ICJTQ1QiLAogIGZlYXR1cmVzID0gaW50ZXJzZWN0KHJvd25hbWVzKHggPSByZWZlcmVuY2UkbWFwKSwgVmFyaWFibGVGZWF0dXJlcyhvYmplY3QgPSBxdWVyeSkpLAogIGRpbXMgPSAxOjUwLAogIG4udHJlZXMgPSAyMCwKICBtYXBwaW5nLnNjb3JlLmsgPSAxMDAKKQoKIyBUcmFuc2ZlciBjZWxsIHR5cGUgbGFiZWxzIGFuZCBpbXB1dGUgcHJvdGVpbiBleHByZXNzaW9uCiMKIyBUcmFuc2ZlcnJlZCBsYWJlbHMgYXJlIGluIG1ldGFkYXRhIGNvbHVtbnMgbmFtZWQgInByZWRpY3RlZC4qIgojIFRoZSBtYXhpbXVtIHByZWRpY3Rpb24gc2NvcmUgaXMgaW4gYSBtZXRhZGF0YSBjb2x1bW4gbmFtZWQgInByZWRpY3RlZC4qLnNjb3JlIgojIFRoZSBwcmVkaWN0aW9uIHNjb3JlcyBmb3IgZWFjaCBjbGFzcyBhcmUgaW4gYW4gYXNzYXkgbmFtZWQgInByZWRpY3Rpb24uc2NvcmUuKiIKIyBUaGUgaW1wdXRlZCBhc3NheSBpcyBuYW1lZCAiaW1wQURUIiBpZiBjb21wdXRlZAoKcmVmZGF0YSA8LSBsYXBwbHkoWCA9ICJjZWxsdHlwZS5sMiIsIGZ1bmN0aW9uKHgpIHsKICByZWZlcmVuY2UkbWFwW1t4LCBkcm9wID0gVFJVRV1dCn0pCm5hbWVzKHggPSByZWZkYXRhKSA8LSAiY2VsbHR5cGUubDIiCmlmIChUUlVFKSB7CiAgcmVmZGF0YVtbImltcEFEVCJdXSA8LSBHZXRBc3NheURhdGEoCiAgICBvYmplY3QgPSByZWZlcmVuY2UkbWFwW1snQURUJ11dLAogICAgc2xvdCA9ICdkYXRhJwogICkKfQpxdWVyeSA8LSBUcmFuc2ZlckRhdGEoCiAgcmVmZXJlbmNlID0gcmVmZXJlbmNlJG1hcCwKICBxdWVyeSA9IHF1ZXJ5LAogIGRpbXMgPSAxOjUwLAogIGFuY2hvcnNldCA9IGFuY2hvcnMsCiAgcmVmZGF0YSA9IHJlZmRhdGEsCiAgbi50cmVlcyA9IDIwLAogIHN0b3JlLndlaWdodHMgPSBUUlVFCikKCiMgQ2FsY3VsYXRlIHRoZSBlbWJlZGRpbmdzIG9mIHRoZSBxdWVyeSBkYXRhIG9uIHRoZSByZWZlcmVuY2UgU1BDQQpxdWVyeSA8LSBJbnRlZ3JhdGVFbWJlZGRpbmdzKAogIGFuY2hvcnNldCA9IGFuY2hvcnMsCiAgcmVmZXJlbmNlID0gcmVmZXJlbmNlJG1hcCwKICBxdWVyeSA9IHF1ZXJ5LAogIHJlZHVjdGlvbnMgPSAicGNhcHJvamVjdCIsCiAgcmV1c2Uud2VpZ2h0cy5tYXRyaXggPSBUUlVFCikKCiMgQ2FsY3VsYXRlIHRoZSBxdWVyeSBuZWlnaGJvcnMgaW4gdGhlIHJlZmVyZW5jZQojIHdpdGggcmVzcGVjdCB0byB0aGUgaW50ZWdyYXRlZCBlbWJlZGRpbmdzCnF1ZXJ5W1sicXVlcnlfcmVmLm5uIl1dIDwtIEZpbmROZWlnaGJvcnMoCiAgb2JqZWN0ID0gRW1iZWRkaW5ncyhyZWZlcmVuY2UkbWFwW1sicmVmRFIiXV0pLAogIHF1ZXJ5ID0gRW1iZWRkaW5ncyhxdWVyeVtbImludGVncmF0ZWRfZHIiXV0pLAogIHJldHVybi5uZWlnaGJvciA9IFRSVUUsCiAgbDIubm9ybSA9IFRSVUUKKQoKIyBUaGUgcmVmZXJlbmNlIHVzZWQgaW4gdGhlIGFwcCBpcyBkb3duc2FtcGxlZCBjb21wYXJlZCB0byB0aGUgcmVmZXJlbmNlIG9uIHdoaWNoCiMgdGhlIFVNQVAgbW9kZWwgd2FzIGNvbXB1dGVkLiBUaGlzIHN0ZXAsIHVzaW5nIHRoZSBoZWxwZXIgZnVuY3Rpb24gTk5UcmFuc2Zvcm0sCiMgY29ycmVjdHMgdGhlIE5laWdoYm9ycyB0byBhY2NvdW50IGZvciB0aGUgZG93bnNhbXBsaW5nLgpxdWVyeSA8LSBOTlRyYW5zZm9ybSgKICBvYmplY3QgPSBxdWVyeSwKICBtZXRhLmRhdGEgPSByZWZlcmVuY2UkbWFwW1tdXQopCgojIFByb2plY3QgdGhlIHF1ZXJ5IHRvIHRoZSByZWZlcmVuY2UgVU1BUC4KcXVlcnlbWyJwcm9qLnVtYXAiXV0gPC0gUnVuVU1BUCgKICBvYmplY3QgPSBxdWVyeVtbInF1ZXJ5X3JlZi5ubiJdXSwKICByZWR1Y3Rpb24ubW9kZWwgPSByZWZlcmVuY2UkbWFwW1sicmVmVU1BUCJdXSwKICByZWR1Y3Rpb24ua2V5ID0gJ1VNQVBfJwopCgoKIyBDYWxjdWxhdGUgbWFwcGluZyBzY29yZSBhbmQgYWRkIHRvIG1ldGFkYXRhCnF1ZXJ5IDwtIEFkZE1ldGFEYXRhKAogIG9iamVjdCA9IHF1ZXJ5LAogIG1ldGFkYXRhID0gTWFwcGluZ1Njb3JlKGFuY2hvcnMgPSBhbmNob3JzKSwKICBjb2wubmFtZSA9ICJtYXBwaW5nLnNjb3JlIgopCgojIFZJU1VBTElaQVRJT05TCgojIEZpcnN0IHByZWRpY3RlZCBtZXRhZGF0YSBmaWVsZCwgY2hhbmdlIHRvIHZpc3VhbGl6ZSBvdGhlciBwcmVkaWN0ZWQgbWV0YWRhdGEKaWQgPC0gImNlbGx0eXBlLmwyIlsxXQpwcmVkaWN0ZWQuaWQgPC0gcGFzdGUwKCJwcmVkaWN0ZWQuIiwgaWQpCgojIERpbVBsb3Qgb2YgdGhlIHJlZmVyZW5jZQpEaW1QbG90KG9iamVjdCA9IHJlZmVyZW5jZSRwbG90LCByZWR1Y3Rpb24gPSAicmVmVU1BUCIsIGdyb3VwLmJ5ID0gaWQsIGxhYmVsID0gVFJVRSkgKyBOb0xlZ2VuZCgpCgojIERpbVBsb3Qgb2YgdGhlIHF1ZXJ5LCBjb2xvcmVkIGJ5IHByZWRpY3RlZCBjZWxsIHR5cGUKRGltUGxvdChvYmplY3QgPSBxdWVyeSwgcmVkdWN0aW9uID0gInByb2oudW1hcCIsIGdyb3VwLmJ5ID0gcHJlZGljdGVkLmlkLCBsYWJlbCA9IFRSVUUpICsgTm9MZWdlbmQoKQoKIyBQbG90IHRoZSBzY29yZSBmb3IgdGhlIHByZWRpY3RlZCBjZWxsIHR5cGUgb2YgdGhlIHF1ZXJ5CkZlYXR1cmVQbG90KG9iamVjdCA9IHF1ZXJ5LCBmZWF0dXJlcyA9IHBhc3RlMChwcmVkaWN0ZWQuaWQsICIuc2NvcmUiKSwgcmVkdWN0aW9uID0gInByb2oudW1hcCIpClZsblBsb3Qob2JqZWN0ID0gcXVlcnksIGZlYXR1cmVzID0gcGFzdGUwKHByZWRpY3RlZC5pZCwgIi5zY29yZSIpLCBncm91cC5ieSA9IHByZWRpY3RlZC5pZCkgKyBOb0xlZ2VuZCgpCgojIFBsb3QgdGhlIG1hcHBpbmcgc2NvcmUKRmVhdHVyZVBsb3Qob2JqZWN0ID0gcXVlcnksIGZlYXR1cmVzID0gIm1hcHBpbmcuc2NvcmUiLCByZWR1Y3Rpb24gPSAicHJvai51bWFwIikKVmxuUGxvdChvYmplY3QgPSBxdWVyeSwgZmVhdHVyZXMgPSAibWFwcGluZy5zY29yZSIsIGdyb3VwLmJ5ID0gcHJlZGljdGVkLmlkKSArIE5vTGVnZW5kKCkKCiMgUGxvdCB0aGUgcHJlZGljdGlvbiBzY29yZSBmb3IgdGhlIGNsYXNzIENEMTYgTW9ubwpGZWF0dXJlUGxvdChvYmplY3QgPSBxdWVyeSwgZmVhdHVyZXMgPSAiQ0QxNiBNb25vIiwgcmVkdWN0aW9uID0gInByb2oudW1hcCIpClZsblBsb3Qob2JqZWN0ID0gcXVlcnksIGZlYXR1cmVzID0gIkNEMTYgTW9ubyIsIGdyb3VwLmJ5ID0gcHJlZGljdGVkLmlkKSArIE5vTGVnZW5kKCkKCiMgUGxvdCBhbiBSTkEgZmVhdHVyZQpGZWF0dXJlUGxvdChvYmplY3QgPSBxdWVyeSwgZmVhdHVyZXMgPSAiR05MWSIsIHJlZHVjdGlvbiA9ICJwcm9qLnVtYXAiKQpWbG5QbG90KG9iamVjdCA9IHF1ZXJ5LCBmZWF0dXJlcyA9ICJHTkxZIiwgZ3JvdXAuYnkgPSBwcmVkaWN0ZWQuaWQpICsgTm9MZWdlbmQoKQoKIyBQbG90IGFuIGltcHV0ZWQgcHJvdGVpbiBmZWF0dXJlCmlmIChUUlVFKSB7CiAgRmVhdHVyZVBsb3Qob2JqZWN0ID0gcXVlcnksIGZlYXR1cmVzID0gIkNEMy0xIiwgcmVkdWN0aW9uID0gInByb2oudW1hcCIpCiAgVmxuUGxvdChvYmplY3QgPSBxdWVyeSwgZmVhdHVyZXMgPSAiQ0QzLTEiLCBncm91cC5ieSA9IHByZWRpY3RlZC5pZCkgKyBOb0xlZ2VuZCgpCn0KCmBgYAoKV2UgbG9hZCBpbiB0aGUgcHJlZGljdGVkIGNlbGwgdHlwZXMgYnkgQXppbXV0aCBzbyB0aGF0IGl0IGNhbiBiZSBjb21wYXJlZCB0byB0aGUgYW5ub3RhdGlvbnMgYmFzZWQgb24gQW15J3MgZGF0YS4KYGBge3J9CiMgbG9hZCBpbiB0aGUgcHJlZGljdGVkIGNlbGwgdHlwZXMgYnkgQXppbXV0aAphemltdXRoLmZpbGVwYXRoPC0iL2hvbWUveWFueGl0aW5nL0RvY3VtZW50cy9SZXNlYXJjaC9HUkFEU19TQVJDX1BCTUMvc2NSTkEtc2VxL1NldXJhdC9hemltdXRoL2F6aW11dGhfcHJlZC50c3YiCmF6aW11dGgucHJlZDwtcmVhZF90c3YoYXppbXV0aC5maWxlcGF0aCkKYXppbXV0aC5wcmVkPC1hcy5kYXRhLmZyYW1lKGF6aW11dGgucHJlZCkKcm93bmFtZXMoYXppbXV0aC5wcmVkKTwtYXMuY2hhcmFjdGVyKGF6aW11dGgucHJlZFssMV0pCmBgYAoKIyMjIyBJbnRlZ3JhdGUgd2l0aCBBbXkncyBkYXRhCgpXZSBvYnRhaW5lZCBhbm90aGVyIFBCTUMgMTBYIGRhdGEgYW5ub3RhdGVkIGluIEthbWluc2tpIGxhYiBzbyB0aGF0IHdlIGNhbiBpbnRlZ3JhdGUgdGhpcyBkYXRhc2V0IHdpdGggdGhlIGFubm90YXRlZCBkYXRhIHRvIGFubm90YXRlIHRoZSBjZWxscyBpbiB0aGlzIGRhdGFzZXQuIFRoZSBzZXVyYXQgb2JqZWN0IGNvbnRhaW5pbmcgdGhlIGFubm90YXRlZCBQQk1DIDEweCBkYXRhIGlzIHVuZGVyIC9ob21lL3h5NDgvc2NyYXRjaF9wYWxtZXIvQW15X1BCTUMgb24gZ3JhY2UgaHBjLiBXZSByYW4gdGhpcyBzZWN0aW9uIG9mIGNvZGVzIG9uIGhwYy4KCiMjIyMjIGRhdGEgbG9hZGluZwoKV2UgZmlyc3QgbG9hZCBpbiB0aGUgYW5ub3RhdGVkIFBCTUMgMTB4IGRhdGEgZnJvbSBBbXkuCmBgYHtyIGZpZy53aWR0aD0xMCxmaWcuaGVpZ2h0PTEwfQpteS5kaXN0aW5jdC5jb2xvcnM8LWMoJyNlNjE5NGInLCAnIzNjYjQ0YicsICcjZmZlMTE5JywgJyM0MzYzZDgnLCAnI2Y1ODIzMScsICcjOTExZWI0JywgJyM0NmYwZjAnLCAnI2YwMzJlNicsICcjYmNmNjBjJywgJyNmYWJlYmUnLCAnIzAwODA4MCcsICcjZTZiZWZmJywgJyM5YTYzMjQnLCAnI2ZmZmFjOCcsICcjODAwMDAwJywgJyNhYWZmYzMnLCAnIzgwODAwMCcsICcjZmZkOGIxJywgJyMwMDAwNzUnLCAnIzgwODA4MCcsICcjZmZmZmZmJywgJyMwMDAwMDAnKQoKZGF0YS5maWxlcGF0aDwtIi9ob21lL3lhbnhpdGluZy9kcml2ZXJfR3JhY2Uvc2NyYXRjaF9wYWxtZXIvQW15X1BCTUMvMDEuMDMuMjMuY29udHJvbHMuZm9yLlhpdGluZy5yZHMiCmFteS4xMHg8LXJlYWRSRFMoZGF0YS5maWxlcGF0aCxyZWZob29rID0gTlVMTCkKYW15LjEweDwtc3Vic2V0KGFteS4xMHgsY2VsbHM9IHJvd25hbWVzKGFteS4xMHhAbWV0YS5kYXRhKVshaXMubmEoYW15LjEweEBtZXRhLmRhdGEkU2FtcGxlSUQpXSkKdGVtcC5udW08LWFzLm1hdHJpeCh0YWJsZShhbXkuMTB4QG1ldGEuZGF0YSRTYW1wbGVJRCkpWywxXQphbXkuMTB4PC1zdWJzZXQoYW15LjEweCxjZWxscz1yb3duYW1lcyhhbXkuMTB4QG1ldGEuZGF0YSlbYW15LjEweEBtZXRhLmRhdGEkU2FtcGxlSUQlaW4lbmFtZXModGVtcC5udW0pW3RlbXAubnVtPj03MDBdXSkKCkRlZmF1bHRBc3NheShhbXkuMTB4KTwtImludGVncmF0ZWQiCkRpbVBsb3QoYW15LjEweCwgcmVkdWN0aW9uID0gInVtYXAiLGdyb3VwLmJ5PSJBbm5vdGF0aW9uXzA5MDgyMiIsY29scyA9IG15LmRpc3RpbmN0LmNvbG9ycykKRGltUGxvdChhbXkuMTB4LCByZWR1Y3Rpb24gPSAidW1hcCIsZ3JvdXAuYnk9IlNhbXBsZUlEIixjb2xzID0gbXkuZGlzdGluY3QuY29sb3JzKQpgYGAKCldlIHRoZW4gbG9hZCBpbiB0aGUgaW50ZWdyYXRlZCBzZXVyYXQgb2JqZWN0IG9mIG91ciBkYXRhLgoKYGBge3J9CmRhdGEuZmlsZXBhdGg8LSIvaG9tZS95YW54aXRpbmcvRG9jdW1lbnRzL1Jlc2VhcmNoL0dSQURTX1NBUkNfUEJNQy9zY1JOQS1zZXEvU2V1cmF0L2ludGVncmF0ZWRfYW5hbHlzaXMvc2V1cmF0X29iamVjdF9pbnRlZ3JhdGVkX2FsbGNlbGxzX2NlbGxjbHVzdGVyaW5nX25vU2luZ2xlUi5yZHMiCnNhbXBsZTEuY29tYmluZWQ8LXJlYWRSRFMoZGF0YS5maWxlcGF0aCxyZWZob29rPU5VTEwpCiMgZm9jdXMgb24gc2FtcGxlcyB3aXRoID49NzAwIGNlbGxzIGluIHRvdGFsCnRlbXAubnVtPC1hcy5tYXRyaXgodGFibGUoc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEkc2FtcGxlLm5hbWVzKSlbLDFdCnNhbXBsZTEuY29tYmluZWQ8LXN1YnNldChzYW1wbGUxLmNvbWJpbmVkLGNlbGxzPXJvd25hbWVzKHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhKVtzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSRzYW1wbGUubmFtZXMlaW4lbmFtZXModGVtcC5udW0pW3RlbXAubnVtPj03MDBdXSkKYGBgCgojIyMjIyBNZXJnZSBjb3VudHMKTWVyZ2UgdGhlIHJhdyBjb3VudCBkYXRhIGZyb20gdGhlc2UgdHdvIHNvdXJjZXMgZm9yIGludGVncmF0aW9uIGFuYWx5c2lzLgoKYGBge3J9CnRlbXAubmFtZXM8LWludGVyc2VjdChyb3duYW1lcyhzYW1wbGUxLmNvbWJpbmVkQGFzc2F5cyRSTkFAY291bnRzKSxyb3duYW1lcyhhbXkuMTB4QGFzc2F5cyRSTkFAY291bnRzKSkKbWVyZ2VkLmNvdW50czwtY2JpbmQoc2FtcGxlMS5jb21iaW5lZEBhc3NheXMkUk5BQGNvdW50c1t0ZW1wLm5hbWVzLF0sYW15LjEweEBhc3NheXMkUk5BQGNvdW50c1t0ZW1wLm5hbWVzLF0pCm1lcmdlZC5tZXRhZGF0YTwtcmJpbmQoYXMubWF0cml4KHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhWyxjKCJkaXNlYXNlIiwic2FtcGxlLm5hbWVzIildKSxhcy5tYXRyaXgoYW15LjEweEBtZXRhLmRhdGFbLGMoImRpc2Vhc2UiLCJzYW1wbGVJRCIpXSkpCnRlbXA8LWMocmVwKE5BLG5yb3coc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEpKSxhbXkuMTB4QG1ldGEuZGF0YSRBbm5vdGF0aW9uXzA5MDgyMikKbWVyZ2VkLm1ldGFkYXRhPC1jYmluZChtZXJnZWQubWV0YWRhdGEsdGVtcCkKY29sbmFtZXMobWVyZ2VkLm1ldGFkYXRhKVszXTwtIkFubm90YXRpb25fMDkwODIyIgp0ZW1wLnByZWQ8LWMocmVwKE5BLG5yb3coc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEpKSxyZXAoTkEsbnJvdyhhbXkuMTB4QG1ldGEuZGF0YSkpKQpuYW1lcyh0ZW1wLnByZWQpPC1jKHJvd25hbWVzKHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhKSxyb3duYW1lcyhhbXkuMTB4QG1ldGEuZGF0YSkpCnRlbXAubmFtZXM8LWludGVyc2VjdChyb3duYW1lcyhzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSkscm93bmFtZXMoYXppbXV0aC5wcmVkKSkKdGVtcC5wcmVkW3RlbXAubmFtZXNdPC1hemltdXRoLnByZWRbdGVtcC5uYW1lcywicHJlZGljdGVkLmNlbGx0eXBlLmwyIl0KCm1lcmdlZC5tZXRhZGF0YTwtY2JpbmQobWVyZ2VkLm1ldGFkYXRhLHRlbXAucHJlZCkKY29sbmFtZXMobWVyZ2VkLm1ldGFkYXRhKVs0XTwtIkFubm90YXRpb25fYXppbXV0aCIKCm1lcmdlZC5kYXRhPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzPW1lcmdlZC5jb3VudHMscHJvamVjdD0iTWVyZ2VkU2V1cmF0T2JqZWN0Iixhc3NheT0iUk5BIixtaW4uY2VsbHM9MyxtaW4uZmVhdHVyZXMgPSAyMDApCm1lcmdlZC5kYXRhW1sicGVyY2VudC5tdCJdXSA8LSBQZXJjZW50YWdlRmVhdHVyZVNldChtZXJnZWQuZGF0YSwgcGF0dGVybiA9ICJeTVQtIikKCmNhdCgiXG4iKQpjYXQoIk9yaWdpbmFsbHksIHRoZXJlIGFyZSAiLG5yb3cobWVyZ2VkLmNvdW50cyksIiBnZW5lcyBhbmQgIixuY29sKG1lcmdlZC5jb3VudHMpLCIgY2VsbHMgaW4gdGhlIGRhdGEuXG4iLHNlcD0iIikKY2F0KCJBZnRlciB0aGUgZmlyc3Qgc3RlcCBmaWx0ZXJpbmcsIHRoZXJlIGFyZSAiLG5yb3cobWVyZ2VkLmRhdGFAYXNzYXlzJFJOQUBkYXRhKSwiIGdlbmVzIGFuZCAiLG5jb2wobWVyZ2VkLmRhdGFAYXNzYXlzJFJOQUBkYXRhKSwiIGNlbGxzIGluIHRoZSBmaWx0ZXJlZCBkYXRhLlxuIixzZXA9IiIpCgpybShtZXJnZWQuY291bnRzKQojZ2ModmVyYm9zZT1GKQppbnZpc2libGUoZ2MoKSkKCgptZXJnZWQuZGF0YSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBtZXJnZWQuZGF0YSwgbWV0YWRhdGEgPSBtZXJnZWQubWV0YWRhdGFbLCJkaXNlYXNlIl0sIGNvbC5uYW1lID0gImRpc2Vhc2UiKQptZXJnZWQuZGF0YSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBtZXJnZWQuZGF0YSwgbWV0YWRhdGEgPSBtZXJnZWQubWV0YWRhdGFbLCJzYW1wbGUubmFtZXMiXSwgY29sLm5hbWUgPSAic2FtcGxlLm5hbWVzIikKbWVyZ2VkLmRhdGEgPC0gQWRkTWV0YURhdGEob2JqZWN0ID0gbWVyZ2VkLmRhdGEsIG1ldGFkYXRhID0gbWVyZ2VkLm1ldGFkYXRhWywiQW5ub3RhdGlvbl8wOTA4MjIiXSwgY29sLm5hbWUgPSAiQW5ub3RhdGlvbl8wOTA4MjIiKQptZXJnZWQuZGF0YSA8LSBBZGRNZXRhRGF0YShvYmplY3QgPSBtZXJnZWQuZGF0YSwgbWV0YWRhdGEgPSBtZXJnZWQubWV0YWRhdGFbLCJBbm5vdGF0aW9uX2F6aW11dGgiXSwgY29sLm5hbWUgPSAiQW5ub3RhdGlvbl9hemltdXRoIikKdGVtcDwtcmVwKCIiLG5yb3cobWVyZ2VkLmRhdGFAbWV0YS5kYXRhKSkKdGVtcFtzdWJzdHIobWVyZ2VkLmRhdGFAbWV0YS5kYXRhJHNhbXBsZS5uYW1lcywxLDEpPT0iRyJdPC0iY291cnRuZXkiCnRlbXBbc3Vic3RyKG1lcmdlZC5kYXRhQG1ldGEuZGF0YSRzYW1wbGUubmFtZXMsMSwxKT09IkMiXTwtImFteSIKbWVyZ2VkLmRhdGEgPC0gQWRkTWV0YURhdGEob2JqZWN0ID0gbWVyZ2VkLmRhdGEsIG1ldGFkYXRhID0gdGVtcCwgY29sLm5hbWUgPSAic3R1ZHkiKQoKYGBgCgojIyMjIyBpbnRlZ3JhdGlvbiBhbmFseXNpcwpTcGxpdCB0aGUgd2hvbGUgZGF0YXNldCBiYXNlZCBvbiBzdWJqZWN0IElELiBGaW5kIGFuY2hvcnMgdG8gaW50ZWdyYXRlIGFsbCB0aGUgZGF0YXNldHMgdG9nZXRoZXIuCgpgYGB7cn0Kc2FtcGxlMS5yYXc8LW1lcmdlZC5kYXRhCnNhbXBsZTEubGlzdDwtU3BsaXRPYmplY3Qoc2FtcGxlMS5yYXcsc3BsaXQuYnk9InN0dWR5IikKI3NhbXBsZTEubGlzdDwtU3BsaXRPYmplY3Qoc2FtcGxlMS5yYXcsc3BsaXQuYnk9InNhbXBsZS5uYW1lcyIpCnNhbXBsZTEubGlzdDwtbGFwcGx5KFg9c2FtcGxlMS5saXN0LEZVTj1mdW5jdGlvbih4KXsKICB4PC1Ob3JtYWxpemVEYXRhKHgpCiAgeDwtRmluZFZhcmlhYmxlRmVhdHVyZXMoeCxzZWxlY3Rpb24ubWV0aG9kPSJ2c3QiLG5mZWF0dXJlcz0yMDAwKQp9KQp0ZW1wLmNlbGxudW08LXVubGlzdChsYXBwbHkoc2FtcGxlMS5saXN0LG5jb2wpKQoKIyBpbmNyZWFzZSB0aGUgZ2xvYmFsIG1heFNpemUKb3B0aW9ucyhmdXR1cmUuZ2xvYmFscy5tYXhTaXplPSA0MDE5NDMwNDAwMCkKZmVhdHVyZXMgPC0gU2VsZWN0SW50ZWdyYXRpb25GZWF0dXJlcyhvYmplY3QubGlzdCA9IHNhbXBsZTEubGlzdCkKc2FtcGxlMS5saXN0IDwtIGxhcHBseShYID0gc2FtcGxlMS5saXN0LCBGVU4gPSBmdW5jdGlvbih4KSB7CiAgICB4IDwtIFNjYWxlRGF0YSh4LCBmZWF0dXJlcyA9IGZlYXR1cmVzLCB2ZXJib3NlID0gRkFMU0UpCiAgICB4IDwtIFJ1blBDQSh4LCBmZWF0dXJlcyA9ZmVhdHVyZXMsIG5wY3M9NDAsdmVyYm9zZT1GKQp9KQojc2FtcGxlMS5hbmNob3JzPC1GaW5kSW50ZWdyYXRpb25BbmNob3JzKHNhbXBsZTEubGlzdFt0ZW1wLmNlbGxudW0+PTkyXSxrLmZpbHRlcj05MixkaW1zPTE6NDAsdmVyYm9zZT1GQUxTRSkKc2FtcGxlMS5hbmNob3JzPC1GaW5kSW50ZWdyYXRpb25BbmNob3JzKG9iamVjdC5saXN0PXNhbXBsZTEubGlzdCxhbmNob3IuZmVhdHVyZXM9ZmVhdHVyZXMsIHJlZHVjdGlvbj0icnBjYSIsdmVyYm9zZT1GQUxTRSkKIyByZWZlcmVuY2U9YygxLDIpOiBodHRwczovL3NhdGlqYWxhYi5vcmcvc2V1cmF0L2FyY2hpdmUvdjMuMi9pbnRlZ3JhdGlvbi5odG1sCiNzYW1wbGUxLmNvbWJpbmVkPC1JbnRlZ3JhdGVEYXRhKGFuY2hvcnNldD1zYW1wbGUxLmFuY2hvcnMsZmVhdHVyZXMudG8uaW50ZWdyYXRlPXJvd25hbWVzKHNhbXBsZTEucmF3QGFzc2F5cyRSTkFAY291bnRzKSxkaW1zPTE6NDAsdmVyYm9zZT1GQUxTRSkKIyxrLmZpbHRlcj00MCxkaW1zPTE6NDAKbWVyZ2VkLmNvbWJpbmVkPC1JbnRlZ3JhdGVEYXRhKGFuY2hvcnNldD1zYW1wbGUxLmFuY2hvcnMsdmVyYm9zZT1GQUxTRSkKCmBgYAoKCgojIyMjIyBjZWxsIGNsdXN0ZXJpbmcKQ2x1c3RlciB0aGUgY2VsbHMgdXNpbmcgdGhlIGludGVncmF0ZWQgZGF0YS4KCmBgYHtyfQpEZWZhdWx0QXNzYXkobWVyZ2VkLmNvbWJpbmVkKSA8LSAiaW50ZWdyYXRlZCIKIyBSdW4gdGhlIHN0YW5kYXJkIHdvcmtmbG93IGZvciB2aXN1YWxpemF0aW9uIGFuZCBjbHVzdGVyaW5nCm1lcmdlZC5jb21iaW5lZCA8LSBTY2FsZURhdGEobWVyZ2VkLmNvbWJpbmVkLCB2ZXJib3NlID0gRkFMU0UpCm1lcmdlZC5jb21iaW5lZCA8LSBSdW5QQ0EobWVyZ2VkLmNvbWJpbmVkLCBucGNzID0gNDAsIHZlcmJvc2UgPSBGQUxTRSkKIyB0LVNORSBhbmQgQ2x1c3RlcmluZwptZXJnZWQuY29tYmluZWQgPC0gUnVuVU1BUChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJwY2EiLCBkaW1zID0gMTo0MCkKbWVyZ2VkLmNvbWJpbmVkIDwtIEZpbmROZWlnaGJvcnMobWVyZ2VkLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAicGNhIiwgZGltcyA9IDE6NDApCm1lcmdlZC5jb21iaW5lZCA8LSBGaW5kQ2x1c3RlcnMobWVyZ2VkLmNvbWJpbmVkLCByZXNvbHV0aW9uID0gMS4yKQpgYGAKIyMjIyMgZGF0YSBzYXZpbmcKV2Ugc2F2ZSB0aGUgcmVzdWx0IGluIGEgcmRzIGZpbGUuCmBgYHtyfQpob21lLmRpcjwtIi9ob21lL3lhbnhpdGluZy9Eb2N1bWVudHMvUmVzZWFyY2gvR1JBRFNfU0FSQ19QQk1DIgpvdXRwdXQuZGlyPC1maWxlLnBhdGgoaG9tZS5kaXIsInNjUk5BLXNlcS9TZXVyYXQiKQp0ZW1wPC1maWxlLnBhdGgob3V0cHV0LmRpciwiaW50ZWdyYXRlZF9hbXkiKQppZihmaWxlLmV4aXN0cyh0ZW1wKT09Ril7CiAgZGlyLmNyZWF0ZSh0ZW1wKQp9Cm91dHB1dC5maWxlcGF0aDwtZmlsZS5wYXRoKG91dHB1dC5kaXIsImludGVncmF0ZWRfYW15Iiwic2V1cmF0X29iamVjdF9pbnRlZ3JhdGVkX21lcmdlZF9zdHVkeS5yZHMiKQpzYXZlUkRTKG1lcmdlZC5jb21iaW5lZCxmaWxlPW91dHB1dC5maWxlcGF0aCkKY2F0KCJXZSBzYXZlZCB0aGUgaW50ZWdyYXRlZCBkYXRhIGJhc2VkIG9uIHN0dWRpZXMgZm9yIGFsbCBjZWxscyBhcyAiLG91dHB1dC5maWxlcGF0aCwiXG4iLHNlcD0iIikKb3V0cHV0LmZpbGVwYXRoPC1maWxlLnBhdGgob3V0cHV0LmRpciwiaW50ZWdyYXRlZF9hbXkiLCJzZXVyYXRfb2JqZWN0X29yaWdpbmFsX21lcmdlZF9zdHVkeS5yZHMiKQpzYXZlUkRTKG1lcmdlZC5kYXRhLGZpbGU9b3V0cHV0LmZpbGVwYXRoKQpjYXQoIldlIHNhdmVkIHRoZSBvcmlnaW5hbCBtZXJnZWQgY291bnQgbWF0cml4IGJhc2VkIG9uIHN0dWRpZXMgZm9yIGFsbCBjZWxscyBhcyAiLG91dHB1dC5maWxlcGF0aCwiXG4iLHNlcD0iIikKYGBgCgoKIyMjIyMgdmlzdWFsaXphdGlvbgoKR2VuZXJhdGUgVU1BUHMgb2YgdGhlIENvdXJ0bmV5IGFuZCBBbXkgZGF0YXNldHMgdG8gY29tcGFyZSB0aGUgY2VsbCB0eXBlcyBwcmVkaWN0ZWQgYnkgQXppbXV0aCBhbmQgYnkgQW15LgpgYGB7cixmaWcud2lkdGg9MTAsZmlnLmhlaWdodD0xMH0KbGlicmFyeShjb3dwbG90KQpsaWJyYXJ5KHJhbmRvbWNvbG9SKQpuIDwtIDMxCnRlbXAuY29sb3JzLjMxIDwtIGRpc3RpbmN0Q29sb3JQYWxldHRlKG4pCgp0ZW1wLmN0PC1tZXJnZWQuY29tYmluZWRAbWV0YS5kYXRhJEFubm90YXRpb25fMDkwODIyCnRlbXAuY3RbaXMubmEodGVtcC5jdCldPC1tZXJnZWQuY29tYmluZWRAbWV0YS5kYXRhJEFubm90YXRpb25fYXppbXV0aFtpcy5uYSh0ZW1wLmN0KV0KbWVyZ2VkLmNvbWJpbmVkPC1BZGRNZXRhRGF0YShtZXJnZWQuY29tYmluZWQsbWV0YWRhdGE9dGVtcC5jdCxjb2wubmFtZT0iY3QucHJlZCIpCgpwMTwtRGltUGxvdChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgY2VsbHM9cm93bmFtZXMobWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSlbbWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSRzdHVkeT09ImNvdXJ0bmV5Il0sZ3JvdXAuYnk9ImN0LnByZWQiLHNodWZmbGUgPSBUUlVFLGNvbHMgPSB0ZW1wLmNvbG9ycy4zMSkrbGFicyh0aXRsZSA9ICJDb3VydG5leSAoQXppbXV0aCkiKQpwMjwtRGltUGxvdChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgY2VsbHM9cm93bmFtZXMobWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSlbbWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSRzdHVkeT09ImFteSJdLGdyb3VwLmJ5PSJjdC5wcmVkIixzaHVmZmxlPVRSVUUsY29scyA9IHRlbXAuY29sb3JzLjMxKStsYWJzKHRpdGxlID0gIkFteSAoQW5ub3RhdGVkKSIpCgojRGltUGxvdChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnk9InNhbXBsZS5uYW1lcyIsbGFiZWwgPSBUUlVFLHNwbGl0LmJ5PSJzdHVkeSIpCiNEaW1QbG90KG1lcmdlZC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieT0iQW5ub3RhdGlvbl8wOTA4MjIiLCBsYWJlbCA9IFRSVUUsY29scz1teS5kaXN0aW5jdC5jb2xvcnMsc3BsaXQuYnk9InN0dWR5IikKI3A0PC1EaW1QbG90KG1lcmdlZC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieT0ic2luZ2xlci5ocGNhLmNsdXN0ZXIiLCBsYWJlbCA9IFRSVUUsY29scz1teS5kaXN0aW5jdC5jb2xvcnMpCiNwNTwtRGltUGxvdChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgZ3JvdXAuYnk9ImRpc2Vhc2UiLCBsYWJlbCA9IFRSVUUsY29scz1teS5kaXN0aW5jdC5jb2xvcnMpCmdyaWQuYXJyYW5nZShwMSxwMixuY29sPTEpCmBgYAoKYGBge3IsZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9MTR9CnAxPC1EaW1QbG90KG1lcmdlZC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBjZWxscz1yb3duYW1lcyhtZXJnZWQuY29tYmluZWRAbWV0YS5kYXRhKVttZXJnZWQuY29tYmluZWRAbWV0YS5kYXRhJHN0dWR5PT0iY291cnRuZXkiXSxncm91cC5ieT0iY3QucHJlZCIsc2h1ZmZsZSA9IFRSVUUsY29scyA9IHRlbXAuY29sb3JzLjMxLGxhYmVsID0gVFJVRSkrbGFicyh0aXRsZSA9ICJDb3VydG5leSAoQXppbXV0aCkiKQpwMjwtRGltUGxvdChtZXJnZWQuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgY2VsbHM9cm93bmFtZXMobWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSlbbWVyZ2VkLmNvbWJpbmVkQG1ldGEuZGF0YSRzdHVkeT09ImFteSJdLGdyb3VwLmJ5PSJjdC5wcmVkIixzaHVmZmxlPVRSVUUsY29scyA9IHRlbXAuY29sb3JzLjMxLGxhYmVsPVRSVUUpK2xhYnModGl0bGUgPSAiQW15IChBbm5vdGF0ZWQpIikKCiNEaW1QbG90KG1lcmdlZC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieT0ic2FtcGxlLm5hbWVzIixsYWJlbCA9IFRSVUUsc3BsaXQuYnk9InN0dWR5IikKI0RpbVBsb3QobWVyZ2VkLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5PSJBbm5vdGF0aW9uXzA5MDgyMiIsIGxhYmVsID0gVFJVRSxjb2xzPW15LmRpc3RpbmN0LmNvbG9ycyxzcGxpdC5ieT0ic3R1ZHkiKQojcDQ8LURpbVBsb3QobWVyZ2VkLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGdyb3VwLmJ5PSJzaW5nbGVyLmhwY2EuY2x1c3RlciIsIGxhYmVsID0gVFJVRSxjb2xzPW15LmRpc3RpbmN0LmNvbG9ycykKI3A1PC1EaW1QbG90KG1lcmdlZC5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBncm91cC5ieT0iZGlzZWFzZSIsIGxhYmVsID0gVFJVRSxjb2xzPW15LmRpc3RpbmN0LmNvbG9ycykKZ3JpZC5hcnJhbmdlKHAxLHAyLG5jb2w9MSkKYGBgCgoKCgoKI29sZApWaXN1YWxpemUgdGhlIGludGVncmF0ZWQgZGF0YSBieSBzcGxpdHRpbmcgaXQgdG8gZGlmZmVyZW50IGNsdXN0ZXJzLgoKYGBge3IsZmlnLndpZHRoPTEwLGZpZy5oZWlnaHQ9NTB9CmZpZy5saXN0PC1saXN0KCkKY2VsbHR5cGUubmFtZXM8LWFzLm51bWVyaWModW5pcXVlKGFzLmNoYXJhY3RlcihJZGVudHMoc2FtcGxlMS5jb21iaW5lZCkpKSkKY2VsbHR5cGUubmFtZXM8LXNvcnQoY2VsbHR5cGUubmFtZXMsZGVjcmVhc2luZz1GKQpmb3IoaSBpbiAxOmxlbmd0aChjZWxsdHlwZS5uYW1lcykpewogIHRlbXAubWFwPC1hcy5jaGFyYWN0ZXIoSWRlbnRzKHNhbXBsZTEuY29tYmluZWQpKQogIHRlbXAubWFwW3RlbXAubWFwIT1jZWxsdHlwZS5uYW1lc1tpXV09Im90aGVyIgogIHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhJHRlbXAubWFwPC10ZW1wLm1hcAogIGZpZy5saXN0W1syKihpLTEpKzFdXTwtRGltUGxvdChzYW1wbGUxLmNvbWJpbmVkLCByZWR1Y3Rpb24gPSAidW1hcCIsIGxhYmVsID0gRkFMU0UsZ3JvdXAuYnk9ImlkZW50IikKICAjZmlnLmxpc3RbWzIqaV1dPC1EaW1QbG90KHNhbXBsZTEuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIiwgbGFiZWwgPSBGQUxTRSxncm91cC5ieT0idGVtcC5tYXAiLGNvbHM9YyhteS5kaXN0aW5jdC5jb2xvcnNbaV0sImdyZXkiKSkKICBmaWcubGlzdFtbMippXV08LURpbVBsb3Qoc2FtcGxlMS5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLCBsYWJlbCA9IEZBTFNFLGdyb3VwLmJ5PSJ0ZW1wLm1hcCIsY29scz1jKCJyZWQiLCJncmV5IikpCn0KZG8uY2FsbChncmlkLmFycmFuZ2UsYyhmaWcubGlzdCxuY29sPTIpKQpgYGAKClZpc3VhbGl6ZSB0aGUgaW50ZWdyYXRlZCBkYXRhIGJ5IHNwbGl0dGluZyBpdCB0byBkaWZmZXJlbnQgc3ViamVjdHMgYW5kIGRpZmZlcmVudCBjZWxsIHR5cGUgYW5ub3RhdGlvbiAob2xkKS4KCmBgYHtyLGZpZy53aWR0aD0xNixmaWcuaGVpZ2h0PTI0fQpmaWcubGlzdDwtbGlzdCgpCmNlbGx0eXBlLm5hbWVzPC11bmlxdWUoYXMuY2hhcmFjdGVyKHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhJHNpbmdsZXIuaHBjYS5jbHVzdGVyKSkKY2VsbHR5cGUubmFtZXM8LXNvcnQoY2VsbHR5cGUubmFtZXMsZGVjcmVhc2luZz1UKQpmb3IoaSBpbiAxOmxlbmd0aChjZWxsdHlwZS5uYW1lcykpewogIHRlbXAubWFwPC1hcy5jaGFyYWN0ZXIoc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEkc2luZ2xlci5ocGNhLmNsdXN0ZXIpCiAgdGVtcC5tYXBbdGVtcC5tYXAhPWNlbGx0eXBlLm5hbWVzW2ldXTwtInpvdGhlciIKICBzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSR0ZW1wLm1hcDwtdGVtcC5tYXAKICBmaWcubGlzdFtbaV1dPC1EaW1QbG90KHNhbXBsZTEuY29tYmluZWQsIHJlZHVjdGlvbiA9ICJ1bWFwIixncm91cC5ieT0idGVtcC5tYXAiLGxhYmVsID0gRkFMU0UscHQuc2l6ZT0wLjUsY29scz1jKG15LmRpc3RpbmN0LmNvbG9yc1tpXSwiZ3JleSIpKQp9CmRvLmNhbGwoZ3JpZC5hcnJhbmdlLGMoZmlnLmxpc3QsbmNvbD0yKSkKYGBgCgojIyMjIENlbGwgdHlwaW5nIHVzaW5nIFNpbmdsZVIgYW5ub3RhdGlvbgoKV2UgcnVuIFNpbmdsZVIgKHYgMS4yLjApIGFnYWluIGJhc2VkIG9uIHRoZSBjbHVzdGVyaW5nIHJlc3VsdHMgYmFzZWQgb24gdGhlIGludGVncmF0ZWQgZGF0YSBhbmQgdmlzdWFsaXplIHRoZSBkYXRhIHVzaW5nIHRoZSBuZXcgY2VsbCB0eXBpbmcgYW5ub3RhdGlvbi4KCmBgYHtyIGZpZy53aWR0aD0xMixmaWcuaGVpZ2h0PTh9CiMgcnVuIFNpbmdsZVIgd2l0aCBIUENBIGFzIHJlZmVyZW5jZSBkYXRhc2V0LgpsaWJyYXJ5KFNpbmdsZVIpCmxpYnJhcnkoc2NSTkFzZXEpCmxpYnJhcnkoc2NhdGVyKQpocGNhLnNlIDwtIEh1bWFuUHJpbWFyeUNlbGxBdGxhc0RhdGEoKQpocGNhLnNlCiNoRVNDcyA8LSBMYU1hbm5vQnJhaW5EYXRhKCdodW1hbi1lcycpCm15LnRlbXA8LUNyZWF0ZVNldXJhdE9iamVjdChjb3VudHM9c2FtcGxlMS5jb21iaW5lZEBhc3NheXMkUk5BQGNvdW50cyxwcm9qZWN0ID0gIkNodXBwU3B1dHVtIixtaW4uY2VsbHMgPSAwLCAgbWluLmZlYXR1cmVzID0gMCkKdGVtcDwtYXMuU2luZ2xlQ2VsbEV4cGVyaW1lbnQobXkudGVtcCxhc3NheT0iUk5BIikKdGVtcDwtbG9nTm9ybUNvdW50cyh0ZW1wKQpzYW1wbGUxLnNpbmdsZXIuaHBjYTwtU2luZ2xlUih0ZXN0PXRlbXAscmVmPWhwY2Euc2UsbGFiZWxzPWhwY2Euc2UkbGFiZWwuZmluZSxtZXRob2Q9ImNsdXN0ZXIiLGNsdXN0ZXJzPWFzLmNoYXJhY3RlcihJZGVudHMoc2FtcGxlMS5jb21iaW5lZCkpKQpwbG90U2NvcmVIZWF0bWFwKHNhbXBsZTEuc2luZ2xlci5ocGNhKQpgYGAKCgpEYXRhIHZpc3VsaXphdGlvbiBsYWJlbGxlZCBieSB0aGUgbmV3IFNpbmdsZVIgcmVzdWx0cyBvbiB0aGUgY29tYmluZWQgZGF0YS4KYGBge3J9CmNsdXN0ZXIubmFtZXM8LXJvd25hbWVzKHNhbXBsZTEuc2luZ2xlci5ocGNhKQpjbHVzdGVyLmxhYmVsczwtc2FtcGxlMS5zaW5nbGVyLmhwY2EkcHJ1bmVkLmxhYmVscwp0ZW1wLm1hcDwtYXMuY2hhcmFjdGVyKElkZW50cyhzYW1wbGUxLmNvbWJpbmVkKSkKdGVtcC5tYXA8LXBseXI6Om1hcHZhbHVlcyh4PXRlbXAubWFwLGZyb209Y2x1c3Rlci5uYW1lcyx0bz1jbHVzdGVyLmxhYmVscykKc2FtcGxlMS5jb21iaW5lZDwtQWRkTWV0YURhdGEoc2FtcGxlMS5jb21iaW5lZCxtZXRhZGF0YT10ZW1wLm1hcCxjb2wubmFtZT0ic2luZ2xlci5ocGNhLmNsdXN0ZXIuY29tYmluZWQiKQpgYGAKCgpHZXQgdGhlIG1haW4gY2VsbCB0eXBlIGxhYmVscyBhbmQgc2F2ZSB0aGUgYW5ub3RhdGVkIHNldXJhdCBSIG9iamVjdC4KYGBge3J9CiMgZ2V0IHRoZSBtYWluIGxhYmVscwpteS50ZW1wPC1DcmVhdGVTZXVyYXRPYmplY3QoY291bnRzPXNhbXBsZTEuY29tYmluZWRAYXNzYXlzJFJOQUBjb3VudHMscHJvamVjdCA9ICJDaHVwcFNwdXR1bSIsbWluLmNlbGxzID0gMCwgIG1pbi5mZWF0dXJlcyA9IDApCnRlbXA8LWFzLlNpbmdsZUNlbGxFeHBlcmltZW50KG15LnRlbXAsYXNzYXk9IlJOQSIpCnRlbXA8LWxvZ05vcm1Db3VudHModGVtcCkKc2FtcGxlMS5zaW5nbGVyLmhwY2E8LVNpbmdsZVIodGVzdD10ZW1wLHJlZj1ocGNhLnNlLGxhYmVscz1ocGNhLnNlJGxhYmVsLm1haW4sbWV0aG9kPSJjbHVzdGVyIixjbHVzdGVycz1hcy5jaGFyYWN0ZXIoSWRlbnRzKHNhbXBsZTEuY29tYmluZWQpKSkKcGxvdFNjb3JlSGVhdG1hcChzYW1wbGUxLnNpbmdsZXIuaHBjYSkKYGBgCgoKYGBge3J9CmNsdXN0ZXIubmFtZXM8LXJvd25hbWVzKHNhbXBsZTEuc2luZ2xlci5ocGNhKQpjbHVzdGVyLmxhYmVsczwtc2FtcGxlMS5zaW5nbGVyLmhwY2EkcHJ1bmVkLmxhYmVscwp0ZW1wLm1hcDwtYXMuY2hhcmFjdGVyKElkZW50cyhzYW1wbGUxLmNvbWJpbmVkKSkKdGVtcC5tYXA8LXBseXI6Om1hcHZhbHVlcyh4PXRlbXAubWFwLGZyb209Y2x1c3Rlci5uYW1lcyx0bz1jbHVzdGVyLmxhYmVscykKc2FtcGxlMS5jb21iaW5lZDwtQWRkTWV0YURhdGEoc2FtcGxlMS5jb21iaW5lZCxtZXRhZGF0YT10ZW1wLm1hcCxjb2wubmFtZT0ic2luZ2xlci5ocGNhLmNsdXN0ZXIubWFpbi5jb21iaW5lZCIpCm91dHB1dC5maWxlcGF0aDwtZmlsZS5wYXRoKG91dHB1dC5zdWJkaXIsInNldXJhdF9vYmplY3RfaW50ZWdyYXRlZF9hbGxjZWxsc19jZWxsY2x1c3RlcmluZ19zaW5nbGVyX2hwY2EucmRzIikKc2F2ZVJEUyhzYW1wbGUxLmNvbWJpbmVkLGZpbGU9b3V0cHV0LmZpbGVwYXRoKQpgYGAKClZpc3VsaXppbmcgdGhlIFNpbmdsZVIgY2VsbCB0eXBpbmcgcmVzdWx0cyBvbiB0aGUgY29tYmluZWQgVU1BUC4KYGBge3IsZmlnLndpZHRoPTE0LGZpZy5oZWlnaHQ9MjR9CmZpZy5saXN0PC1saXN0KCkKY2VsbHR5cGUubmFtZXM8LXVuaXF1ZShhcy5jaGFyYWN0ZXIoc2FtcGxlMS5jb21iaW5lZEBtZXRhLmRhdGEkc2luZ2xlci5ocGNhLmNsdXN0ZXIuY29tYmluZWQpKQpjZWxsdHlwZS5uYW1lczwtc29ydChjZWxsdHlwZS5uYW1lcyxkZWNyZWFzaW5nPVQpCmZvcihpIGluIDE6bGVuZ3RoKGNlbGx0eXBlLm5hbWVzKSl7CiAgdGVtcC5tYXA8LWFzLmNoYXJhY3RlcihzYW1wbGUxLmNvbWJpbmVkQG1ldGEuZGF0YSRzaW5nbGVyLmhwY2EuY2x1c3Rlci5jb21iaW5lZCkKICB0ZW1wLm1hcFt0ZW1wLm1hcCE9Y2VsbHR5cGUubmFtZXNbaV1dPC0iem90aGVyIgogIHNhbXBsZTEuY29tYmluZWRAbWV0YS5kYXRhJHRlbXAubWFwPC10ZW1wLm1hcAogIGZpZy5saXN0W1tpXV08LURpbVBsb3Qoc2FtcGxlMS5jb21iaW5lZCwgcmVkdWN0aW9uID0gInVtYXAiLGdyb3VwLmJ5PSJ0ZW1wLm1hcCIsbGFiZWwgPSBGQUxTRSxwdC5zaXplPTAuNSxjb2xzPWMobXkuZGlzdGluY3QuY29sb3JzW2ldLCJncmV5IikpCn0KZG8uY2FsbChncmlkLmFycmFuZ2UsYyhmaWcubGlzdCxuY29sPTIpKQpgYGAKCgo=